import { useId } from "react";
import type { WaterfallProps } from "./schema";

/** エントランスの時差。間隔は global.css の --stagger-step で統一 */
const stagger = (i: number, baseMs = 0) =>
  `calc(${baseMs}ms + ${i} * var(--stagger-step))`;

type StepInput = { value: number; isTotal?: boolean };
export type BridgeBar = { start: number; end: number; isTotal: boolean };

/**
 * 累計を各ステップの棒に写す。isTotal はその値を累計のリセット地点にする
 * （位置は schema で強制しないため、途中にある isTotal は中間小計として自然に働く）
 */
export function bridgeBars(steps: StepInput[]): BridgeBar[] {
  let acc = 0;
  return steps.map((s) => {
    if (s.isTotal) {
      acc = s.value;
      return { start: 0, end: s.value, isTotal: true };
    }
    const start = acc;
    acc += s.value;
    return { start, end: acc, isTotal: false };
  });
}

/**
 * 値域は必ず0を含める。total バーは「0からの高さ」で描く決定（構造バー）と
 * 整合させるため — 0を含めないと value と表示高さのスケールが食い違う
 */
export function chartDomain(bars: BridgeBar[]) {
  const values = bars.flatMap((b) => [b.start, b.end]);
  const min = Math.min(0, ...values);
  const max = Math.max(0, ...values);
  return { min, max, span: max - min };
}

export function zeroLinePct(domain: { max: number; span: number }): number {
  return domain.span === 0 ? 100 : (domain.max / domain.span) * 100;
}

/** span=0（空配列・全ゼロ）でも0除算しない。0ラインの位置を高さ0のバーとして返す */
export function barGeometry(
  bar: BridgeBar,
  domain: { max: number; span: number },
): { topPct: number; heightPct: number } {
  if (domain.span === 0) return { topPct: zeroLinePct(domain), heightPct: 0 };
  const top = domain.max - Math.max(bar.start, bar.end);
  const height = Math.abs(bar.end - bar.start);
  return { topPct: (top / domain.span) * 100, heightPct: (height / domain.span) * 100 };
}

/** 寄与度%の分母。total（アンカー）は「増減」ではないので除外する */
export function totalSwing(steps: StepInput[]): number {
  return steps.filter((s) => !s.isTotal).reduce((sum, s) => sum + Math.abs(s.value), 0);
}

/** swing=0（差分が全部0、または差分ステップが無い）は0を返す（NaN/Infinity禁止） */
export function contributionPct(value: number, swing: number): number {
  return swing === 0 ? 0 : (Math.abs(value) / swing) * 100;
}

/**
 * 寄与度順ビュー用の並べ替え。total は除外。originalIndex は安定したキーとして残す
 */
export function rankedSteps<T extends StepInput>(
  steps: T[],
): (T & { originalIndex: number })[] {
  return steps
    .map((s, originalIndex) => ({ ...s, originalIndex }))
    .filter((s) => !s.isTotal)
    .sort((a, b) => Math.abs(b.value) - Math.abs(a.value));
}

const HIGHLIGHT_GRADIENT = "bg-linear-to-br from-tertiary to-accent-violet";

/**
 * 色は「意味」でなく「構造/向き」を表す — total=primary（構造）、増加=tertiary、
 * 減少=secondary。success/error は良し悪し専用（DESIGN.md）なのでここでは使わない
 */
export function barColorClass(step: {
  isTotal?: boolean;
  value: number;
  highlight?: boolean;
}): string {
  if (step.highlight) return HIGHLIGHT_GRADIENT;
  if (step.isTotal) return "bg-primary";
  if (step.value > 0) return "bg-tertiary";
  if (step.value < 0) return "bg-secondary";
  return "bg-secondary/40";
}

/** 描画専用の最小可視表示。生のgeometry/%は汚さない（process-flow の stepWidths と同じ発想） */
export const MIN_VISIBLE_PCT = 0.6;
export const visiblePct = (pct: number) => Math.max(pct, MIN_VISIBLE_PCT);

/** 差分値の符号付き表示。ブリッジと寄与度順の両方で使う */
const signedValue = (value: number) => `${value > 0 ? "+" : ""}${value}`;

export default function Waterfall({
  title,
  subtitle,
  steps,
  instanceId,
}: WaterfallProps) {
  const autoId = useId();
  const uid = instanceId ?? autoId;

  const bars = bridgeBars(steps);
  const domain = chartDomain(bars);
  const swing = totalSwing(steps);
  const ranked = rankedSteps(steps);
  const zero = zeroLinePct(domain);
  const notes = steps.filter((step) => step.note);

  return (
    <section
      data-waterfall-root="true"
      className="group/waterfall mx-auto max-w-slide bg-surface p-12 font-sans text-on-surface"
    >
      {/* 表示切替は CSS のみ（radio + :has()）。成果物の単一ファイル制約を守る */}
      <input
        type="radio"
        name={uid}
        id={`${uid}-bridge`}
        className="v-bridge sr-only"
        defaultChecked
      />
      <input type="radio" name={uid} id={`${uid}-ranked`} className="v-ranked sr-only" />

      <header className="mb-8 flex flex-wrap items-start justify-between gap-4">
        <div>
          <h1 className="text-h1">{title}</h1>
          {subtitle && (
            <p className="mt-2 text-body-sm text-secondary">{subtitle}</p>
          )}
        </div>
        <div className="inline-flex rounded-full border border-outline bg-surface p-1 shadow-card">
          <label
            htmlFor={`${uid}-bridge`}
            className="cursor-pointer rounded-full px-4 py-1.5 text-label text-secondary group-has-[.v-bridge:checked]/waterfall:bg-primary group-has-[.v-bridge:checked]/waterfall:text-on-primary group-has-[.v-bridge:focus-visible]/waterfall:ring-2 group-has-[.v-bridge:focus-visible]/waterfall:ring-tertiary"
          >
            ブリッジ
          </label>
          <label
            htmlFor={`${uid}-ranked`}
            className="cursor-pointer rounded-full px-4 py-1.5 text-label text-secondary group-has-[.v-ranked:checked]/waterfall:bg-primary group-has-[.v-ranked:checked]/waterfall:text-on-primary group-has-[.v-ranked:focus-visible]/waterfall:ring-2 group-has-[.v-ranked:focus-visible]/waterfall:ring-tertiary"
          >
            寄与度順
          </label>
        </div>
      </header>

      {steps.length === 0 ? (
        <p className="text-body-sm text-secondary">ステップが未設定</p>
      ) : (
        <>
          {/* ── ブリッジビュー: 累計配置の浮動バー ──
              data-view はテスト専用フック。v-bridge/v-ranked は input・label・
              このコンテナの3箇所に登場するため class 部分一致では一意に選べない */}
          <div
            data-view="bridge"
            className="hidden group-has-[.v-bridge:checked]/waterfall:block"
          >
            <div className="relative h-72 rounded-lg border border-outline bg-surface">
              <div
                aria-hidden="true"
                className="absolute inset-x-0 border-t border-dashed border-outline"
                style={{ top: `${zero}%` }}
              />
              <div className="flex h-full items-stretch">
                {steps.map((step, i) => {
                  const geom = barGeometry(bars[i], domain);
                  return (
                    <div
                      key={i}
                      data-waterfall-step={i}
                      className="group/step relative flex-1 animate-pop px-1"
                      style={{ animationDelay: stagger(i, 400) }}
                    >
                      <div
                        className={`absolute inset-x-2 rounded-sm ${barColorClass(step)}`}
                        style={{
                          top: `${geom.topPct}%`,
                          height: `${visiblePct(geom.heightPct)}%`,
                        }}
                      />
                    </div>
                  );
                })}
              </div>
            </div>
            <div className="mt-3 flex items-stretch">
              {steps.map((step, i) => (
                <div key={i} data-waterfall-record="true" className="flex-1 px-1 text-center text-label text-secondary">
                  <p>{step.label}</p>
                  <p className="mt-1 font-mono">
                    {step.isTotal ? bars[i].end : signedValue(step.value)}
                  </p>
                </div>
              ))}
            </div>
          </div>

          {/* ── 寄与度順ビュー: total を除いた同じ steps を |value| 降順で並べ替え ── */}
          <div
            data-view="ranked"
            className="hidden flex-col gap-3 group-has-[.v-ranked:checked]/waterfall:flex"
          >
            {ranked.length === 0 ? (
              <p className="text-body-sm text-secondary">差分ステップがありません</p>
            ) : (
              ranked.map((step, i) => {
                const pct = contributionPct(step.value, swing);
                return (
                  <div
                    key={step.originalIndex}
                    className="group/step animate-rise"
                    style={{ animationDelay: stagger(i) }}
                  >
                    <div className="flex items-baseline justify-between gap-2">
                      <span className="text-body-sm">{step.label}</span>
                      <span className="shrink-0 text-label text-secondary">
                        {signedValue(step.value)}（{pct.toFixed(1)}%）
                      </span>
                    </div>
                    <div className="mt-1 h-3 w-full rounded-full bg-surface-subtle">
                      <div
                        className={`h-full rounded-full ${barColorClass(step)}`}
                        style={{ width: `${visiblePct(pct)}%` }}
                      />
                    </div>
                  </div>
                );
              })
            )}
            <p className="text-right text-label text-secondary">
              増減の絶対値が大きい順。総変動に占める割合(%)を表示
            </p>
          </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((step, index) => (
              <article key={index} data-note-item="true" className="rounded-md bg-surface p-4">
                <h3 className="text-label">{step.label}</h3>
                <p className="mt-1 font-mono text-label text-secondary">
                  {step.isTotal ? `累計 ${step.value}` : `${signedValue(step.value)} ・ 寄与度 ${contributionPct(step.value, swing).toFixed(1)}%`}
                </p>
                <p className="mt-2 text-body-sm">{step.note}</p>
              </article>
            ))}
          </div>
        </details>
      )}
    </section>
  );
}
