import { useId } from "react";
import type { StrategyMapProps, StrategyObjective } from "./schema";

type StrategyPerspective = StrategyMapProps["perspectives"][number];
type StrategyLink = StrategyMapProps["links"][number];

const round = (value: number) => Number(value.toFixed(2));

export function rankStrategyGaps(objectives: StrategyObjective[]) {
  return objectives
    .map((objective, index) => ({
      ...objective,
      gap: objective.impact * (6 - objective.current),
      inputIndex: index,
    }))
    .sort((a, b) => b.gap - a.gap || a.inputIndex - b.inputIndex);
}

export function strategyGeometry(
  perspectives: StrategyPerspective[],
  objectives: StrategyObjective[],
  links: StrategyLink[],
) {
  const nodes = objectives.map((objective) => {
    const perspectiveIndex = perspectives.findIndex(
      (perspective) => perspective.id === objective.perspectiveId,
    );
    const peers = objectives.filter(
      (item) => item.perspectiveId === objective.perspectiveId,
    );
    const peerIndex = peers.findIndex((item) => item.id === objective.id);
    const x = 22 + ((peerIndex + 1) / (peers.length + 1)) * 56;
    const y = perspectives.length === 1
      ? 50
      : 82 - (perspectiveIndex / (perspectives.length - 1)) * 64;
    const radius = 2.8 + objective.impact * 0.65;
    const gapRadius = radius + 2 + (6 - objective.current) * 0.45;
    return {
      ...objective,
      x: round(x),
      y: round(y),
      radius: round(radius),
      gapRadius: round(gapRadius),
      gap: objective.impact * (6 - objective.current),
    };
  });
  const position = new Map(nodes.map((node) => [node.id, node]));
  const edges = links.map((link, index) => ({
    ...link,
    id: `${link.sourceId}-${link.targetId}-${index}`,
    source: position.get(link.sourceId)!,
    target: position.get(link.targetId)!,
    strokeWidth: round(1 + link.strength * 0.65),
  }));
  return { nodes, edges };
}

export default function StrategyMap({
  title,
  subtitle,
  perspectives,
  objectives,
  links,
  instanceId,
}: StrategyMapProps) {
  // 既定を固定文字列にすると、id を指定せず 2 つ置いたときに radio の name と
  // SVG marker の id を共有してしまう。他の型と同じく useId() に寄せる。
  const autoId = useId();
  const uid = (instanceId ?? autoId).replace(
      // 逃がし文字（`_`）自身も逃がすので単射。捨てる形にすると `a:b` と `a/b` が
      // 同じ id になる。
      /[^a-zA-Z0-9-]/g,
      (c: string) => `_${c.charCodeAt(0).toString(16)}_`,
    ) || "strategy-map";
  const ranked = rankStrategyGaps(objectives);
  const priority = ranked[0];
  const notes = objectives.filter((item) => item.note);
  const geometry = strategyGeometry(perspectives, objectives, links);
  const markerId = `${uid}-causal-arrow`;
  const canvasX = (value: number) => value * 10;
  const canvasY = (value: number) => value * 4.2;

  return (
    <section
      className="asset group/strategy overflow-hidden rounded-xl border border-outline bg-surface p-5 text-primary shadow-card sm:p-8"
      data-layout="strategy-map"
    >
      <header className="border-b border-outline pb-5">
        <p className="font-mono text-label text-tertiary">CAUSE → EFFECT</p>
        <h1 className="mt-2 text-h1 text-balance">{title}</h1>
        {subtitle && <p className="mt-2 text-body text-secondary">{subtitle}</p>}
      </header>

      <div className="mt-5 flex flex-wrap justify-between gap-3">
        <div className="inline-flex rounded-full border border-outline bg-surface-subtle p-1 text-label">
          <label className="rounded-full px-3 py-1.5 has-[:checked]:bg-primary has-[:checked]:text-on-primary">
            <input className="strategy-view-map sr-only" type="radio" name={`${uid}-view`} defaultChecked />因果
          </label>
          <label className="rounded-full px-3 py-1.5 has-[:checked]:bg-primary has-[:checked]:text-on-primary">
            <input className="strategy-view-gap sr-only" type="radio" name={`${uid}-view`} />重点
          </label>
        </div>
        <span className="font-mono text-label text-secondary">RING=GAP · LINE=STRENGTH</span>
      </div>

      <div
        className="mt-5 grid gap-2 border-l-4 border-tertiary bg-gradient-to-r from-tertiary/20 to-transparent px-4 py-3 sm:grid-cols-[auto_1fr_auto] sm:items-center"
        data-strategy-default-priority="true"
      >
        <span className="font-mono text-label text-tertiary">LEVERAGE POINT</span>
        <strong>{priority.label}</strong>
        <span className="font-mono text-h3">GAP {priority.gap}</span>
      </div>

      <div data-strategy-view="map" className="mt-6 group-has-[.strategy-view-gap:checked]/strategy:hidden">
        <svg
          className="hidden h-[26rem] w-full sm:block"
          viewBox="0 0 1000 420"
          role="img"
          aria-label="下位の基盤から上位の成果へ向かう戦略因果マップ"
          data-primary-geometry="strategy-map"
          data-geometry-channels="perspective position impact gap link-strength"
        >
          <defs>
            <marker id={markerId} viewBox="0 0 10 10" refX="8" refY="5" markerWidth="9" markerHeight="9" markerUnits="userSpaceOnUse" orient="auto-start-reverse">
              <path d="M 0 0 L 10 5 L 0 10 z" className="fill-primary" />
            </marker>
          </defs>

          {perspectives.map((perspective, index) => {
            const yPercent = perspectives.length === 1 ? 50 : 82 - (index / (perspectives.length - 1)) * 64;
            const y = canvasY(yPercent);
            return (
              <g key={perspective.id}>
                <line x1="112" x2="972" y1={y} y2={y} className="stroke-outline" vectorEffect="non-scaling-stroke" />
                <text x="12" y={y + 5} className="fill-secondary font-mono text-label">P{String(index + 1).padStart(2, "0")}</text>
              </g>
            );
          })}

          {geometry.edges.map((edge) => {
            const sourceX = canvasX(edge.source.x);
            const sourceY = canvasY(edge.source.y);
            const targetX = canvasX(edge.target.x);
            const targetY = canvasY(edge.target.y);
            const deltaX = targetX - sourceX;
            const deltaY = targetY - sourceY;
            const distance = Math.hypot(deltaX, deltaY) || 1;
            const unitX = deltaX / distance;
            const unitY = deltaY / distance;
            const sourceClearance = edge.source.gapRadius * 2.8 + 3;
            const targetClearance = edge.target.gapRadius * 2.8 + 9;
            const startX = sourceX + unitX * sourceClearance;
            const startY = sourceY + unitY * sourceClearance;
            const endX = targetX - unitX * targetClearance;
            const endY = targetY - unitY * targetClearance;
            const middleY = (startY + endY) / 2;
            const touchesPriority = edge.sourceId === priority.id || edge.targetId === priority.id;
            return (
              <path
                key={edge.id}
                d={`M ${startX} ${startY} C ${startX} ${middleY}, ${endX} ${middleY}, ${endX} ${endY}`}
                fill="none"
                vectorEffect="non-scaling-stroke"
                strokeWidth={edge.strokeWidth}
                markerEnd={`url(#${markerId})`}
                className={touchesPriority ? "stroke-tertiary" : "stroke-primary"}
                data-primary-edge={edge.id}
              />
            );
          })}

          {geometry.nodes.map((node, index) => {
            const x = canvasX(node.x);
            const y = canvasY(node.y);
            const radius = node.radius * 2.8;
            const gapRadius = node.gapRadius * 2.8;
            const circumference = 2 * Math.PI * gapRadius;
            const currentShare = node.current / 5;
            const isPriority = node.id === priority.id;
            const labelOnLeft = node.x < 50;
            const labelX = labelOnLeft ? x - gapRadius - 10 : x + gapRadius + 10;
            const anchor = labelOnLeft ? "end" : "start";
            return (
              <g
                key={node.id}
                tabIndex={0}
                data-strategy-objective={node.id}
                data-primary-record={node.id}
                data-geometry-a={node.x}
                data-geometry-b={node.y}
                data-primary-emphasis={isPriority ? "true" : undefined}
              >
                <title>{`${node.label} · 影響${node.impact} · 現状${node.current} · GAP ${node.gap}`}</title>
                {isPriority && <circle cx={x} cy={y} r={gapRadius + 8} className="fill-tertiary/15" />}
                <circle cx={x} cy={y} r={gapRadius} fill="none" className={isPriority ? "stroke-tertiary" : "stroke-outline"} strokeWidth="2" />
                <circle
                  cx={x}
                  cy={y}
                  r={gapRadius}
                  fill="none"
                  className={isPriority ? "stroke-tertiary" : "stroke-primary"}
                  strokeWidth="3"
                  strokeDasharray={`${round(circumference * currentShare)} ${round(circumference * (1 - currentShare))}`}
                  transform={`rotate(-90 ${x} ${y})`}
                />
                <circle cx={x} cy={y} r={radius} className={isPriority ? "fill-tertiary" : "fill-primary"} />
                <text data-primary-label={node.id} x={labelX} y={y - 4} textAnchor={anchor} className="fill-primary text-label font-semibold">
                  O{String(index + 1).padStart(2, "0")}
                </text>
              </g>
            );
          })}
        </svg>

        <div className="relative space-y-5 pl-7 sm:hidden">
          <span className="absolute bottom-2 left-2 top-2 w-px bg-outline" aria-hidden="true" />
          {perspectives.map((perspective, perspectiveIndex) => (
            <section key={perspective.id} className="relative">
              <h2 className="font-mono text-label text-secondary">{String(perspectiveIndex + 1).padStart(2, "0")} · {perspective.name}</h2>
              <div className="mt-2 space-y-3">
                {objectives.filter((item) => item.perspectiveId === perspective.id).map((objective) => {
                  const score = ranked.find((item) => item.id === objective.id)!;
                  const isPriority = objective.id === priority.id;
                  return (
                    <div key={objective.id} data-strategy-objective={objective.id} data-strategy-mobile-objective={objective.id} className="relative grid grid-cols-[1fr_auto] gap-3 py-1 pl-3">
                      <span className={`absolute -left-[1.65rem] top-2 h-3 w-3 rounded-full ${isPriority ? "bg-tertiary ring-4 ring-tertiary/20" : "bg-primary"}`} aria-hidden="true" />
                      <strong>{objective.label}</strong>
                      <span className="font-mono text-label">I{objective.impact} · G{score.gap}</span>
                    </div>
                  );
                })}
              </div>
            </section>
          ))}
        </div>
      </div>

      <div className="strategy-map-key mt-5 hidden gap-x-6 gap-y-2 border-t border-outline pt-4 sm:grid sm:grid-cols-2 xl:grid-cols-3" aria-label="目標一覧">
        {objectives.map((objective, index) => {
          const perspective = perspectives.find((item) => item.id === objective.perspectiveId)!;
          const score = ranked.find((item) => item.id === objective.id)!;
          return <div key={objective.id} data-strategy-key={objective.id} className="grid grid-cols-[3rem_1fr] gap-2 py-1">
            <strong className="font-mono text-label text-tertiary">O{String(index + 1).padStart(2, "0")}</strong>
            <span>{objective.label}<span className="block text-label text-secondary">P{String(perspectives.findIndex((item) => item.id === objective.perspectiveId) + 1).padStart(2, "0")} · {perspective.name} · I{objective.impact} · C{objective.current} · GAP {score.gap}</span></span>
          </div>;
        })}
      </div>

      <div data-strategy-view="gap-ledger" className="mt-6 hidden group-has-[.strategy-view-gap:checked]/strategy:block">
        <div className="divide-y divide-outline border-y border-outline">
          {ranked.map((objective, index) => (
            <article key={objective.id} data-strategy-objective={objective.id} className={`grid grid-cols-[3rem_1fr_auto] items-center gap-3 py-3 ${index === 0 ? "border-l-4 border-tertiary pl-3" : "pl-4"}`}>
              <span className="font-mono text-label text-secondary">{String(index + 1).padStart(2, "0")}</span>
              <div><strong>{objective.label}</strong><p className="text-label text-secondary">影響{objective.impact} × 現在水準不足{6 - objective.current}</p></div>
              <span className="font-mono text-h3">{objective.gap}</span>
            </article>
          ))}
        </div>
      </div>

      {notes.length > 0 && (
        <details data-inline-disclosure className="mt-6 rounded-lg border border-outline bg-surface-subtle p-4">
          <summary className="cursor-pointer font-semibold">補足を確認（{notes.length}件）</summary>
          {notes.map((item) => <p key={item.id} data-note-item="true" className="mt-3 text-body text-secondary"><strong className="text-primary">{item.label}:</strong> {item.note}</p>)}
        </details>
      )}
    </section>
  );
}
