import { useId } from "react";
import type {
  RaciActivity,
  RaciMark,
  RaciMatrixProps,
} from "./schema";

export type RaciIssueCode =
  | "missing-accountable"
  | "multiple-accountable"
  | "missing-responsible";
export type RaciIssue = {
  activityIndex: number;
  activityLabel: string;
  code: RaciIssueCode;
};

export function activityAuditSummary(activity: {
  assignments: readonly RaciMark[];
}) {
  return {
    accountable: activity.assignments.filter((mark) => mark === "A").length,
    responsible: activity.assignments.filter((mark) => mark === "R").length,
  };
}

export function auditActivities(
  activities: readonly {
    label: string;
    assignments: readonly RaciMark[];
  }[],
): RaciIssue[] {
  return activities.flatMap((activity, activityIndex) => {
    const issues: RaciIssue[] = [];
    const { accountable, responsible } = activityAuditSummary(activity);
    if (accountable === 0) {
      issues.push({
        activityIndex,
        activityLabel: activity.label,
        code: "missing-accountable",
      });
    }
    if (accountable > 1) {
      issues.push({
        activityIndex,
        activityLabel: activity.label,
        code: "multiple-accountable",
      });
    }
    if (responsible === 0) {
      issues.push({
        activityIndex,
        activityLabel: activity.label,
        code: "missing-responsible",
      });
    }
    return issues;
  });
}

export function issueEvidenceRoleIndexes(
  activity: { assignments: readonly RaciMark[] },
  issue: RaciIssue,
) {
  if (issue.code !== "multiple-accountable") return [];
  return activity.assignments.flatMap((mark, roleIndex) =>
    mark === "A" ? [roleIndex] : [],
  );
}

export function issueLabel(code: RaciIssueCode) {
  return (
    {
      "missing-accountable": "A不在",
      "multiple-accountable": "A重複",
      "missing-responsible": "R不在",
    } as const
  )[code];
}

const issueDescription = (code: RaciIssueCode) =>
  (
    {
      "missing-accountable": "最終説明責任を負う役割が定義されていない",
      "multiple-accountable": "最終判断者が複数で、意思決定が競合する",
      "missing-responsible": "実行責任を担う役割が定義されていない",
    } as const
  )[code];

const MARK_STYLES: Record<RaciMark, string> = {
  R: "bg-tertiary text-on-primary",
  A: "bg-primary text-on-primary",
  C: "bg-neutral text-secondary",
  I: "border border-outline bg-surface text-secondary",
  "-": "text-outline",
};

const stagger = (index: number) =>
  `calc(var(--stagger-step) * ${index})`;

function Mark({
  mark,
  activityIndex,
  roleLabel,
  evidence = false,
}: {
  mark: RaciMark;
  activityIndex: number;
  roleLabel: string;
  evidence?: boolean;
}) {
  return (
    <span
      tabIndex={0}
      data-raci-hotspot={activityIndex}
      data-raci-evidence={evidence ? "true" : undefined}
      aria-label={`${roleLabel}: ${mark === "-" ? "割当なし" : mark}`}
      className={`inline-flex size-8 items-center justify-center rounded-full font-mono text-label outline-none transition-[color,background-color,box-shadow,opacity] focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 ${MARK_STYLES[mark]} ${evidence ? "ring-2 ring-tertiary ring-offset-2" : ""}`}
    >
      {mark === "-" ? "·" : mark}
    </span>
  );
}

function ActivityIssueLabels({ issues }: { issues: readonly RaciIssue[] }) {
  if (issues.length === 0) return null;
  return (
    <span className="ml-2 inline-flex flex-wrap gap-1 align-middle">
      {issues.map((issue) => (
        <span
          key={issue.code}
          className="border-l-2 border-tertiary pl-1.5 font-mono text-label text-tertiary"
        >
          {issueLabel(issue.code)}
        </span>
      ))}
    </span>
  );
}

function MobileActivityRecord({
  activity,
  activityIndex,
  roles,
  issues,
  isDefaultPriority,
}: {
  activity: RaciActivity;
  activityIndex: number;
  roles: RaciMatrixProps["roles"];
  issues: readonly RaciIssue[];
  isDefaultPriority: boolean;
}) {
  return (
    <article
      data-raci-activity-record={activityIndex}
      data-raci-surface={activityIndex}
      data-raci-default-priority={isDefaultPriority ? "true" : undefined}
      className={`animate-rise border-b px-1 py-4 transition-[background-color,box-shadow,opacity] first:border-t ${
        isDefaultPriority
          ? "border-tertiary bg-gradient-to-r from-tertiary/10 to-surface"
          : "border-outline"
      }`}
      style={{ animationDelay: stagger(activityIndex) }}
    >
      <div
        tabIndex={0}
        data-raci-hotspot={activityIndex}
        className="flex items-start gap-2 outline-none focus-visible:ring-2 focus-visible:ring-primary"
      >
        <span className="pt-0.5 font-mono text-label text-secondary">
          {String(activityIndex + 1).padStart(2, "0")}
        </span>
        <h2 className="min-w-0 text-body-sm">
          {activity.label}
          <ActivityIssueLabels issues={issues} />
        </h2>
      </div>
      <div className="mt-3 grid grid-cols-2 gap-x-4 gap-y-2 pl-7">
        {activity.assignments.map((mark, roleIndex) => (
          <div
            key={roles[roleIndex].id}
            data-raci-role-index={roleIndex}
            data-raci-mark={mark}
            className="flex min-w-0 items-center justify-between gap-2 border-t border-outline/60 pt-2"
          >
            <span className="min-w-0 text-label text-secondary">
              {roles[roleIndex].label}
            </span>
            <Mark
              mark={mark}
              activityIndex={activityIndex}
              roleLabel={roles[roleIndex].label}
            />
          </div>
        ))}
      </div>
    </article>
  );
}

export default function RaciMatrix({
  title,
  subtitle,
  roles,
  activities,
  instanceId,
}: RaciMatrixProps) {
  const autoId = useId();
  const uid =
    (instanceId ?? autoId).replace(
      // 捨てる操作は単射でないので、schema を通る別々の値が同じ id になる
      // （`sales:a` と `sales/a` がどちらも `salesa`）。同じ文書へ 2 つ置くと
      // id と radio の name が衝突して、片方の操作がもう片方に効く。
      // 逃がし文字（`_`）自身も逃がすので、この変換は単射。
      /[^a-zA-Z0-9-]/g,
      (c) => `_${c.charCodeAt(0).toString(16)}_`,
    ) || "raci";
  const issues = auditActivities(activities);
  const firstIssueActivity = issues[0]?.activityIndex;
  const notes = activities.filter((activity) => activity.note);
  const issuesByActivity = activities.map((_, activityIndex) =>
    issues.filter((issue) => issue.activityIndex === activityIndex),
  );
  const interactionCss = activities
    .map(
      (_, activityIndex) => `
[data-raci-root="${uid}"]:has([data-raci-hotspot="${activityIndex}"]:is(:hover,:focus-visible)) [data-raci-surface="${activityIndex}"] {
  background-color: color-mix(in srgb, var(--color-primary) 9%, var(--color-surface));
  box-shadow: inset 3px 0 0 var(--color-primary);
  color: var(--color-on-surface);
  opacity: 1;
}
[data-raci-root="${uid}"]:has([data-raci-hotspot="${activityIndex}"]:is(:hover,:focus-visible)) [data-raci-default-priority="true"]:not([data-raci-surface="${activityIndex}"]) {
  filter: saturate(.28) brightness(1.08);
}`,
    )
    .join("\n");

  return (
    <section
      data-raci-root={uid}
      className="group/raci mx-auto max-w-slide bg-surface p-12 font-sans text-on-surface"
    >
      <style>{interactionCss}</style>
      <input
        id={`${uid}-matrix`}
        name={`${uid}-view`}
        type="radio"
        defaultChecked
        className="raci-matrix-view sr-only"
      />
      <input
        id={`${uid}-issues`}
        name={`${uid}-view`}
        type="radio"
        className="raci-issues-view sr-only"
      />
      <header className="mb-7 flex flex-wrap items-end justify-between gap-4">
        <div className="max-w-3xl">
          <h1 className="text-h1">{title}</h1>
          {subtitle && (
            <p className="mt-2 text-body-sm text-secondary">{subtitle}</p>
          )}
        </div>
        <div className="flex rounded-sm border border-outline bg-surface-subtle p-1 shadow-card">
          <label
            htmlFor={`${uid}-matrix`}
            className="cursor-pointer rounded-sm px-4 py-1.5 text-label text-secondary group-has-[.raci-matrix-view:checked]/raci:bg-primary group-has-[.raci-matrix-view:checked]/raci:text-on-primary"
          >
            責任分担
          </label>
          <label
            htmlFor={`${uid}-issues`}
            className="cursor-pointer rounded-sm px-4 py-1.5 text-label text-secondary group-has-[.raci-issues-view:checked]/raci:bg-primary group-has-[.raci-issues-view:checked]/raci:text-on-primary"
          >
            統制課題
          </label>
        </div>
      </header>

      <div
        data-testid="raci-matrix"
        className="block group-has-[.raci-issues-view:checked]/raci:hidden"
      >
        <div data-raci-view="matrix-desktop" className="hidden sm:block">
          <table className="w-full table-fixed border-collapse">
            <colgroup>
              <col className="w-[29%]" />
              {roles.map((role) => (
                <col key={role.id} />
              ))}
            </colgroup>
            <thead>
              <tr className="border-y border-outline">
                <th className="px-3 py-3 text-left font-mono text-label text-secondary">
                  ACTIVITY
                </th>
                {roles.map((role) => (
                  <th
                    key={role.id}
                    className="px-1 py-3 text-center text-label text-secondary"
                  >
                    {role.label}
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {activities.map((activity, activityIndex) => {
                const activityIssues = issuesByActivity[activityIndex];
                const isDefaultPriority =
                  activityIndex === firstIssueActivity;
                return (
                  <tr
                    key={activityIndex}
                    data-raci-activity-record={activityIndex}
                    data-raci-surface={activityIndex}
                    data-raci-default-priority={
                      isDefaultPriority ? "true" : undefined
                    }
                    data-has-issue={
                      activityIssues.length > 0 ? "true" : "false"
                    }
                    className={`animate-rise border-b transition-[background-color,box-shadow,opacity] ${
                      isDefaultPriority
                        ? "border-tertiary bg-gradient-to-r from-tertiary/10 to-surface"
                        : "border-outline"
                    }`}
                    style={{ animationDelay: stagger(activityIndex) }}
                  >
                    <th scope="row" className="px-3 py-3 text-left">
                      <span
                        tabIndex={0}
                        data-raci-hotspot={activityIndex}
                        className="block text-body-sm outline-none focus-visible:ring-2 focus-visible:ring-primary"
                      >
                        {activity.label}
                        <ActivityIssueLabels issues={activityIssues} />
                      </span>
                    </th>
                    {activity.assignments.map((mark, roleIndex) => (
                      <td
                        key={roles[roleIndex].id}
                        data-raci-role-index={roleIndex}
                        data-raci-mark={mark}
                        className="px-1 py-3 text-center"
                      >
                        <Mark
                          mark={mark}
                          activityIndex={activityIndex}
                          roleLabel={roles[roleIndex].label}
                        />
                      </td>
                    ))}
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>

        <div data-raci-view="matrix-mobile" className="sm:hidden">
          {activities.map((activity, activityIndex) => (
            <MobileActivityRecord
              key={activityIndex}
              activity={activity}
              activityIndex={activityIndex}
              roles={roles}
              issues={issuesByActivity[activityIndex]}
              isDefaultPriority={activityIndex === firstIssueActivity}
            />
          ))}
        </div>
        <p className="mt-3 text-right font-mono text-label text-secondary">
          R 実行 · A 最終責任 · C 相談 · I 報告
        </p>
      </div>

      <div
        data-testid="raci-issues"
        data-raci-view="issues"
        className="hidden group-has-[.raci-issues-view:checked]/raci:block"
      >
        {issues.length === 0 ? (
          <div className="border-y border-outline bg-surface-subtle p-8 text-center">
            <p className="text-h3">
              責任上の欠陥は検出されませんでした
            </p>
            <p className="mt-2 text-body-sm text-secondary">
              全活動にAが1件、Rが1件以上あります
            </p>
          </div>
        ) : (
          <div>
            {issues.map((issue, issueIndex) => {
              const activity = activities[issue.activityIndex];
              const summary = activityAuditSummary(activity);
              const evidenceIndexes = new Set(
                issueEvidenceRoleIndexes(activity, issue),
              );
              const absence =
                issue.code === "missing-accountable"
                  ? { mark: "A", count: summary.accountable }
                  : issue.code === "missing-responsible"
                    ? { mark: "R", count: summary.responsible }
                    : null;
              return (
                <article
                  key={`${issue.activityIndex}-${issue.code}`}
                  data-priority={issueIndex === 0 ? "true" : "false"}
                  data-raci-default-priority={
                    issueIndex === 0 ? "true" : undefined
                  }
                  data-raci-issue-record={issueIndex}
                  data-raci-issue-activity={issue.activityIndex}
                  data-raci-surface={issue.activityIndex}
                  className={`animate-rise border-b px-1 py-5 transition-[background-color,box-shadow,filter,opacity] first:border-t ${
                    issueIndex === 0
                      ? "border-tertiary bg-gradient-to-r from-tertiary/15 via-surface to-surface"
                      : "border-outline bg-surface"
                  }`}
                  style={{ animationDelay: stagger(issueIndex) }}
                >
                  <div className="grid gap-4 lg:grid-cols-[minmax(11rem,1.3fr)_minmax(20rem,3fr)_minmax(13rem,1.5fr)] lg:items-center">
                    <div
                      tabIndex={0}
                      data-raci-hotspot={issue.activityIndex}
                      className="outline-none focus-visible:ring-2 focus-visible:ring-primary"
                    >
                      <div className="flex items-center gap-2">
                        <span className="font-mono text-label text-secondary">
                          {String(issueIndex + 1).padStart(2, "0")}
                        </span>
                        <span className="border-l-2 border-tertiary pl-2 font-mono text-label text-tertiary">
                          {issueLabel(issue.code)}
                        </span>
                      </div>
                      <h2 className="mt-1 text-body-sm">
                        {issue.activityLabel}
                      </h2>
                    </div>

                    <div className="flex flex-wrap gap-x-4 gap-y-3">
                      {activity.assignments.map((mark, roleIndex) => {
                        const evidence = evidenceIndexes.has(roleIndex);
                        return (
                          <div
                            key={roles[roleIndex].id}
                            data-raci-role-index={roleIndex}
                            data-raci-mark={mark}
                            className={`flex min-w-28 flex-1 items-center justify-between gap-2 border-t pt-2 ${
                              evidence
                                ? "border-tertiary"
                                : "border-outline"
                            }`}
                          >
                            <span className="text-label text-secondary">
                              {roles[roleIndex].label}
                            </span>
                            <Mark
                              mark={mark}
                              activityIndex={issue.activityIndex}
                              roleLabel={roles[roleIndex].label}
                              evidence={evidence}
                            />
                          </div>
                        );
                      })}
                    </div>

                    <div className="border-l-2 border-outline pl-3">
                      <p className="text-body-sm text-secondary">
                        {issueDescription(issue.code)}
                      </p>
                      {absence && (
                        <span
                          data-raci-absence={absence.mark}
                          className="mt-2 inline-flex items-baseline gap-2 font-mono text-label text-tertiary"
                        >
                          <strong className="text-h3">
                            {absence.mark} {absence.count}
                          </strong>
                          <span>/ 必要数 1</span>
                        </span>
                      )}
                      {issue.code === "multiple-accountable" && (
                        <span className="mt-2 block font-mono text-label text-tertiary">
                          A {summary.accountable} / 必要数 1
                        </span>
                      )}
                    </div>
                  </div>
                </article>
              );
            })}
          </div>
        )}
      </div>

      {notes.length > 0 && (
        <details
          data-inline-disclosure="true"
          className="group mt-6 rounded-lg border border-outline bg-surface-subtle"
        >
          <summary className="flex cursor-pointer list-none items-center px-4 py-3 text-label text-secondary focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tertiary">
            <span>補足を確認</span>
            <span className="ml-auto font-mono">{notes.length}件</span>
            <span
              aria-hidden="true"
              className="ml-2 text-tertiary transition-transform group-open:rotate-45"
            >
              ＋
            </span>
          </summary>
          <div className="grid gap-3 border-t border-outline p-4 sm:grid-cols-2">
            {notes.map((activity, index) => (
              <article
                key={index}
                data-note-item="true"
                className="rounded-md bg-surface p-4"
              >
                <h3 className="text-label">{activity.label}</h3>
                <p className="mt-2 text-body-sm">{activity.note}</p>
              </article>
            ))}
          </div>
        </details>
      )}
    </section>
  );
}
