// BlueprintPanel — the full "reproduce this unit end-to-end" detail: the
// complete, copy-pasteable reproduction recipe across the six axes —
//   1. scenario / scene table   2. per-stage verbatim prompts
//   3. composition skeleton      4. model stack + params + cost
//   5. concrete recipes / effects 6. hard assets (downloadable)
//
// Presentation-only (no "use client", no server-only APIs): everything here is
// selectable / downloadable text. It is rendered INSIDE the BlueprintModal
// (#079) rather than dumped inline on the page — the unit page now leads with the
// viewer + similar-units rail (Pinterest browse), and the Blueprint lives behind
// the blue "Blueprint — reproduce this unit" button. Because it has no client
// directive, it can be composed by the client modal as a plain child.
//
// Each axis is exposed as an ADDRESSABLE SECTION (id + title + node) via
// `blueprintSections()` so the two-pane modal (#080) can drive a left rail of
// section names against a right scrollable pane — only sections that carry data
// are returned. `BlueprintBody` keeps composing every section in order for any
// caller that wants the flat dump.
//
// When `blueprint` is undefined the section list is empty and the body renders
// nothing (graceful: units with no published Blueprint show no button + no
// modal).
//
// No visible borders: separation via bg-tint steps + shadow + spacing only.

import type { ReactNode } from "react";
import type {
  Blueprint,
  BlueprintAsset,
  BlueprintAssetKind,
} from "@/lib/library-v2/types";
import { DownloadAssetLink } from "./DownloadAssetLink";

const ASSET_GROUP_LABEL: Record<BlueprintAssetKind, string> = {
  character: "Characters",
  location: "Locations",
  prop: "Props",
  music: "Music",
  ref: "References",
  master: "Masters",
};

const ASSET_GROUP_ORDER: BlueprintAssetKind[] = [
  "master",
  "character",
  "location",
  "prop",
  "music",
  "ref",
];

function fmtUsd(n: number | undefined): string | undefined {
  if (n === undefined || Number.isNaN(n)) return undefined;
  return `$${n.toFixed(n < 1 ? 4 : 2)}`;
}

function fmtBytes(n: number | undefined): string | undefined {
  if (n === undefined || Number.isNaN(n)) return undefined;
  if (n < 1024) return `${n} B`;
  if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
  return `${(n / (1024 * 1024)).toFixed(1)} MB`;
}

/** The blueprint cost rendered as a formatted USD string, for the modal header.
 *  Exported so the modal can show "Total cost to reproduce" near the title. */
export function blueprintCost(blueprint?: Blueprint): string | undefined {
  if (!blueprint) return undefined;
  return fmtUsd(blueprint.costRollupUsd);
}

/** One addressable Blueprint axis — `id` targets the left-rail nav, `title`
 *  labels it, `node` is the rendered content for the right pane. */
export interface BlueprintSection {
  id: string;
  title: string;
  node: ReactNode;
}

/** Factor a blueprint into its present sections (the ones that carry data), in
 *  canonical order. Drives the two-pane modal's left rail + right pane, and the
 *  flat `BlueprintBody`. Returns [] when no blueprint. */
export function blueprintSections(blueprint?: Blueprint): BlueprintSection[] {
  if (!blueprint) return [];

  const { scenario, prompts, composition, modelStack, recipes, assets } = blueprint;

  const hasScenario =
    !!scenario && ((scenario.scenes?.length ?? 0) > 0 || !!scenario.storyboardMd);
  const hasPrompts = prompts.length > 0;
  const hasComposition =
    !!composition &&
    ((composition.components?.length ?? 0) > 0 ||
      (composition.timing?.A?.length ?? 0) > 0 ||
      (composition.timing?.SEG?.length ?? 0) > 0 ||
      !!composition.file);
  const hasModels = modelStack.length > 0;
  const hasRecipes = recipes.length > 0;
  const hasAssets = assets.length > 0;
  const hasOversize = (blueprint.oversizeSkipped?.length ?? 0) > 0;

  const sections: BlueprintSection[] = [];

  if (hasScenario) {
    sections.push({
      id: "scenario",
      title: "Scenario",
      node: (
        <div className="bp-axis">
          <p className="bp-axis-label">Scenario</p>
          {scenario!.scenes && scenario!.scenes.length > 0 && (
            <div className="bp-table-wrap">
              <table className="bp-table">
                <thead>
                  <tr>
                    <th>Scene</th>
                    <th>Label</th>
                    <th>Dur</th>
                    <th>VO</th>
                    <th>SFX</th>
                    <th>Fork</th>
                  </tr>
                </thead>
                <tbody>
                  {scenario!.scenes.map((s) => (
                    <tr key={s.id}>
                      <td className="bp-mono">{s.id}</td>
                      <td>{s.label ?? ""}</td>
                      <td className="bp-mono">
                        {s.durationSec !== undefined ? `${s.durationSec}s` : ""}
                      </td>
                      <td className="bp-vo">{s.vo ?? ""}</td>
                      <td className="bp-mono">{(s.sfx ?? []).join(", ")}</td>
                      <td>
                        {s.fork
                          ? `${s.fork.label}${
                              s.fork.options?.length
                                ? ` → ${s.fork.options.join(" / ")}`
                                : ""
                            }`
                          : ""}
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}
          {scenario!.storyboardMd && (
            <pre className="bp-pre bp-storyboard">{scenario!.storyboardMd}</pre>
          )}
        </div>
      ),
    });
  }

  if (hasPrompts) {
    sections.push({
      id: "prompts",
      title: "Prompts",
      node: (
        <div className="bp-axis">
          <p className="bp-axis-label">Prompts</p>
          <div className="bp-prompts">
            {prompts.map((p, i) => (
              <div key={`${p.stage}-${p.slot ?? i}`} className="bp-prompt">
                <div className="bp-prompt-meta">
                  <span className="bp-tag bp-tag-stage">{p.stage}</span>
                  {p.slot && <span className="bp-tag">{p.slot}</span>}
                  {p.model && <span className="bp-tag bp-tag-model">{p.model}</span>}
                </div>
                <pre className="bp-pre">{p.text}</pre>
                {p.slots && p.slots.length > 0 && (
                  <p className="bp-slots">
                    slots: {p.slots.map((t) => `{{${t}}}`).join(" · ")}
                  </p>
                )}
              </div>
            ))}
          </div>
        </div>
      ),
    });
  }

  if (hasComposition) {
    sections.push({
      id: "composition",
      title: "Composition",
      node: (
        <div className="bp-axis">
          <p className="bp-axis-label">Composition</p>
          <div className="bp-comp">
            {composition!.components && composition!.components.length > 0 && (
              <div className="bp-comp-block">
                <p className="bp-comp-key">Components / registry blocks / overlays</p>
                <div className="bp-pills">
                  {composition!.components.map((c) => (
                    <span key={c} className="bp-pill">
                      {c}
                    </span>
                  ))}
                </div>
              </div>
            )}
            {composition!.timing?.A && composition!.timing.A.length > 0 && (
              <div className="bp-comp-block">
                <p className="bp-comp-key">Scene starts — A[]</p>
                <pre className="bp-pre bp-pre-inline">
                  [{composition!.timing.A.join(", ")}]
                </pre>
              </div>
            )}
            {composition!.timing?.SEG && composition!.timing.SEG.length > 0 && (
              <div className="bp-comp-block">
                <p className="bp-comp-key">Segment durations — SEG[]</p>
                <pre className="bp-pre bp-pre-inline">
                  [{composition!.timing.SEG.join(", ")}]
                </pre>
              </div>
            )}
            {composition!.file && (
              <p className="bp-comp-note">
                Composition file:{" "}
                <span className="bp-mono">{composition!.file}</span>
              </p>
            )}
          </div>
        </div>
      ),
    });
  }

  if (hasModels) {
    sections.push({
      id: "models",
      title: "Model stack",
      node: (
        <div className="bp-axis">
          <p className="bp-axis-label">Model stack</p>
          <div className="bp-table-wrap">
            <table className="bp-table">
              <thead>
                <tr>
                  <th>Stage</th>
                  <th>Model</th>
                  <th>Key params</th>
                  <th>Voice</th>
                  <th>Cost</th>
                </tr>
              </thead>
              <tbody>
                {modelStack.map((m, i) => (
                  <tr key={`${m.stage}-${i}`}>
                    <td className="bp-mono">{m.stage}</td>
                    <td className="bp-mono">{m.model}</td>
                    <td className="bp-params">
                      {m.params && Object.keys(m.params).length > 0
                        ? Object.entries(m.params)
                            .map(([k, v]) => `${k}=${JSON.stringify(v)}`)
                            .join(" · ")
                        : ""}
                    </td>
                    <td className="bp-mono">{m.voiceId ?? ""}</td>
                    <td className="bp-mono">{fmtUsd(m.costUsd) ?? ""}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      ),
    });
  }

  if (hasRecipes) {
    sections.push({
      id: "recipes",
      title: "Recipes",
      node: (
        <div className="bp-axis">
          <p className="bp-axis-label">Recipes</p>
          <div className="bp-prompts">
            {recipes.map((r, i) => (
              <div key={`${r.name}-${i}`} className="bp-prompt">
                <div className="bp-prompt-meta">
                  <span className="bp-tag bp-tag-stage">{r.kind}</span>
                  <span className="bp-tag">{r.name}</span>
                </div>
                {r.command && <pre className="bp-pre">{r.command}</pre>}
                {r.params && Object.keys(r.params).length > 0 && (
                  <p className="bp-slots">
                    {Object.entries(r.params)
                      .map(([k, v]) => `${k}=${JSON.stringify(v)}`)
                      .join(" · ")}
                  </p>
                )}
              </div>
            ))}
          </div>
        </div>
      ),
    });
  }

  if (hasAssets) {
    sections.push({
      id: "assets",
      title: "Hard assets",
      node: (
        <div className="bp-axis">
          <p className="bp-axis-label">Hard assets</p>
          {ASSET_GROUP_ORDER.map((kind) => {
            const group = assets.filter((a) => a.kind === kind);
            if (group.length === 0) return null;
            return (
              <div key={kind} className="bp-asset-group">
                <p className="bp-asset-group-label">{ASSET_GROUP_LABEL[kind]}</p>
                <div className="bp-assets">
                  {group.map((a, i) => (
                    <AssetRow key={`${a.path}-${i}`} asset={a} />
                  ))}
                </div>
              </div>
            );
          })}
        </div>
      ),
    });
  }

  if (hasOversize) {
    sections.push({
      id: "oversize",
      title: "In the source project",
      node: (
        <div className="bp-axis">
          <p className="bp-axis-label">In the source project</p>
          <p className="bp-oversize-note">
            These payload files were too large for Storage and live in the source
            project, not here:
          </p>
          <ul className="bp-oversize-list">
            {blueprint.oversizeSkipped!.map((p) => (
              <li key={p} className="bp-mono">
                {p}
              </li>
            ))}
          </ul>
        </div>
      ),
    });
  }

  return sections;
}

/** The full Blueprint detail, without any section / panel chrome — every present
 *  section composed in order. Meant as a flat fallback dump; the BlueprintModal
 *  drives `blueprintSections()` directly for its two-pane layout. Renders
 *  nothing when no blueprint. */
export function BlueprintBody({ blueprint }: { blueprint?: Blueprint }) {
  const sections = blueprintSections(blueprint);
  if (sections.length === 0) return null;
  return (
    <>
      {sections.map((s) => (
        <div key={s.id}>{s.node}</div>
      ))}
    </>
  );
}

function AssetRow({ asset }: { asset: BlueprintAsset }) {
  const bytes = fmtBytes(asset.bytes);
  if (asset.storageUrl) {
    // Cross-origin Storage URL — blob-fetch download so the click SAVES the file
    // instead of navigating away (#095). Filename from the path's basename.
    const filename = asset.path?.split("/").pop() || asset.slot || "download";
    return (
      <DownloadAssetLink
        className="bp-asset bp-asset-dl"
        href={asset.storageUrl}
        filename={filename}
      >
        <span className="bp-asset-glyph" aria-hidden>
          ↓
        </span>
        <span className="bp-asset-main">
          <span className="bp-asset-name">{asset.slot ?? asset.path}</span>
          <span className="bp-asset-meta">
            {asset.path}
            {bytes ? ` · ${bytes}` : ""}
          </span>
        </span>
        <span className="bp-asset-action">Download</span>
      </DownloadAssetLink>
    );
  }
  return (
    <div className="bp-asset">
      <span className="bp-asset-glyph" aria-hidden>
        ◇
      </span>
      <span className="bp-asset-main">
        <span className="bp-asset-name">{asset.slot ?? asset.path}</span>
        <span className="bp-asset-meta">
          <span className="bp-mono">{asset.path}</span>
          {bytes ? ` · ${bytes}` : ""}
        </span>
      </span>
      <span className="bp-asset-note">in the source project</span>
    </div>
  );
}
