"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import { EstimateStatus } from "@capakraken/shared";
import { computeEvenSpread } from "@capakraken/engine";
import { isSpreadsheetFile } from "~/lib/excel.js";
import { parseScopeImport } from "~/lib/scopeImportParser.js";
import { clsx } from "clsx";
import { formatMoney } from "~/lib/format.js";
import { useFocusTrap } from "~/hooks/useFocusTrap.js";
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
import { ProjectCombobox } from "~/components/ui/ProjectCombobox.js";
import { ResourceCombobox } from "~/components/ui/ResourceCombobox.js";
import { trpc } from "~/lib/trpc/client.js";
import { uuid } from "~/lib/uuid.js";

const INPUT_CLS =
  "w-full rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 outline-none transition focus:border-brand-500 focus:ring-2 focus:ring-brand-100";
const SELECT_CLS = INPUT_CLS;
const LABEL_CLS = "mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-500";
const STEP_LABELS = ["Setup", "Assumptions", "Scope", "Staffing", "Review"];

interface AssumptionRow {
  id: string;
  category: string;
  key: string;
  label: string;
  value: string;
}

interface ScopeRow {
  id: string;
  sequenceNo: number;
  scopeType: string;
  name: string;
  description: string;
}

interface DemandRow {
  id: string;
  name: string;
  roleId: string | null;
  resourceId: string | null;
  hours: string;
  chapter: string;
  costRate: string;
  billRate: string;
  currency: string;
}

interface ProjectOption {
  id: string;
  shortCode: string;
  name: string;
  startDate?: string | Date | null;
  endDate?: string | Date | null;
}

interface RoleOption {
  id: string;
  name: string;
}

interface ResourceOption {
  id: string;
  eid: string;
  displayName: string;
  chapter: string | null;
  currency: string;
  lcrCents: number;
  ucrCents: number;
  roleId: string | null;
  federalState: string | null;
}

function makeAssumption(): AssumptionRow {
  return {
    id: uuid(),
    category: "commercial",
    key: "",
    label: "",
    value: "",
  };
}

function makeScope(sequenceNo = 1): ScopeRow {
  return {
    id: uuid(),
    sequenceNo,
    scopeType: "SHOT",
    name: "",
    description: "",
  };
}

function makeDemand(): DemandRow {
  return {
    id: uuid(),
    name: "",
    roleId: null,
    resourceId: null,
    hours: "8",
    chapter: "",
    costRate: "",
    billRate: "",
    currency: "EUR",
  };
}

function toCents(value: string) {
  const parsed = Number.parseFloat(value);
  if (!Number.isFinite(parsed) || parsed < 0) {
    return 0;
  }
  return Math.round(parsed * 100);
}

function toHours(value: string) {
  const parsed = Number.parseFloat(value);
  if (!Number.isFinite(parsed) || parsed < 0) {
    return 0;
  }
  return parsed;
}

function slugify(value: string) {
  return value
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "_")
    .replace(/^_+|_+$/g, "");
}

export function EstimateWizard({ onClose }: { onClose: () => void }) {
  const [step, setStep] = useState(0);
  const [name, setName] = useState("");
  const [projectId, setProjectId] = useState<string | null>(null);
  const [opportunityId, setOpportunityId] = useState("");
  const [baseCurrency, setBaseCurrency] = useState("EUR");
  const [status, setStatus] = useState<EstimateStatus>(EstimateStatus.DRAFT);
  const [versionLabel, setVersionLabel] = useState("Initial");
  const [versionNotes, setVersionNotes] = useState("");
  const [assumptions, setAssumptions] = useState<AssumptionRow[]>([makeAssumption()]);
  const [scopeItems, setScopeItems] = useState<ScopeRow[]>([makeScope(1)]);
  const [demandLines, setDemandLines] = useState<DemandRow[]>([makeDemand()]);
  const [error, setError] = useState<string | null>(null);
  const [scopeImportWarnings, setScopeImportWarnings] = useState<string[]>([]);

  const panelRef = useRef<HTMLDivElement>(null);
  useFocusTrap(panelRef, true);

  const utils = trpc.useUtils();
  const projectsQuery = trpc.project.list.useQuery({ limit: 200 }, { staleTime: 60_000 });
  const rolesQuery = trpc.role.list.useQuery({ isActive: true }, { staleTime: 60_000 });
  const resourcesQuery = trpc.resource.list.useQuery(
    { limit: 500, includeRoles: true, isActive: true },
    { staleTime: 60_000 },
  );

  const createMutation = trpc.estimate.create.useMutation({
    onSuccess: async () => {
      await utils.estimate.list.invalidate();
      onClose();
    },
    onError: (mutationError) => {
      setError(mutationError.message);
    },
  });

  const projectRows = (projectsQuery.data?.projects ?? []) as unknown as ProjectOption[];
  const roleRows = (rolesQuery.data ?? []) as unknown as RoleOption[];
  const resourceRows = (resourcesQuery.data?.resources ?? []) as unknown as ResourceOption[];

  const projects: ProjectOption[] = projectRows.map((project) => ({
    id: project.id,
    shortCode: project.shortCode,
    name: project.name,
  }));
  const roles: RoleOption[] = roleRows.map((role) => ({
    id: role.id,
    name: role.name,
  }));
  const resources: ResourceOption[] = resourceRows.map((resource) => ({
    id: resource.id,
    eid: resource.eid,
    displayName: resource.displayName,
    chapter: resource.chapter,
    currency: resource.currency,
    lcrCents: resource.lcrCents,
    ucrCents: resource.ucrCents,
    roleId: resource.roleId,
    federalState: resource.federalState,
  }));

  const selectedProject = projectId
    ? projects.find((project) => project.id === projectId) ?? null
    : null;

  const summary = useMemo(() => {
    return demandLines.reduce(
      (accumulator, line) => {
        const hours = toHours(line.hours);
        const costTotalCents = Math.round(hours * toCents(line.costRate));
        const priceTotalCents = Math.round(hours * toCents(line.billRate));

        return {
          totalHours: accumulator.totalHours + hours,
          totalCostCents: accumulator.totalCostCents + costTotalCents,
          totalPriceCents: accumulator.totalPriceCents + priceTotalCents,
        };
      },
      { totalHours: 0, totalCostCents: 0, totalPriceCents: 0 },
    );
  }, [demandLines]);

  const marginCents = summary.totalPriceCents - summary.totalCostCents;
  const marginPercent = summary.totalPriceCents > 0
    ? Math.round((marginCents / summary.totalPriceCents) * 100)
    : 0;

  useEffect(() => {
    function handleKeyDown(event: KeyboardEvent) {
      if (event.key === "Escape") {
        onClose();
      }
    }

    document.addEventListener("keydown", handleKeyDown);
    return () => document.removeEventListener("keydown", handleKeyDown);
  }, [onClose]);

  function updateAssumption(id: string, patch: Partial<AssumptionRow>) {
    setAssumptions((current) =>
      current.map((row) => (row.id === id ? { ...row, ...patch } : row)),
    );
  }

  function updateScopeItem(id: string, patch: Partial<ScopeRow>) {
    setScopeItems((current) =>
      current.map((row) => (row.id === id ? { ...row, ...patch } : row)),
    );
  }

  function updateDemandLine(id: string, patch: Partial<DemandRow>) {
    setDemandLines((current) =>
      current.map((row) => (row.id === id ? { ...row, ...patch } : row)),
    );
  }

  function applyResource(resourceId: string | null, demandLineId: string) {
    const resource = resourceId
      ? resources.find((item) => item.id === resourceId) ?? null
      : null;

    updateDemandLine(demandLineId, {
      resourceId,
      name: resource?.displayName ?? "",
      chapter: resource?.chapter ?? "",
      currency: resource?.currency ?? baseCurrency,
      costRate: resource ? (resource.lcrCents / 100).toFixed(2) : "",
      billRate: resource ? (resource.ucrCents / 100).toFixed(2) : "",
      roleId: resource?.roleId ?? null,
    });
  }

  async function handleScopeImport(event: React.ChangeEvent<HTMLInputElement>) {
    const file = event.target.files?.[0];
    if (!file) return;
    event.target.value = "";

    if (!isSpreadsheetFile(file)) {
      setScopeImportWarnings(["Unsupported file type. Please upload .xlsx, .xls, or .csv."]);
      return;
    }

    try {
      const result = await parseScopeImport(file);
      setScopeImportWarnings(result.warnings);

      if (result.rows.length > 0) {
        const imported: ScopeRow[] = result.rows.map((row) => ({
          id: uuid(),
          sequenceNo: row.sequenceNo,
          scopeType: row.scopeType,
          name: row.name,
          description: row.description,
        }));
        setScopeItems((current) => {
          const nonEmpty = current.filter((item) => item.name.trim());
          return [...nonEmpty, ...imported];
        });
      }
    } catch {
      setScopeImportWarnings(["Failed to parse the file. Please check the format."]);
    }
  }

  function validateStep(targetStep: number) {
    if (targetStep === 1 && !name.trim()) {
      setError("Estimate name is required.");
      return false;
    }

    setError(null);
    return true;
  }

  function goNext() {
    const nextStep = Math.min(step + 1, STEP_LABELS.length - 1);
    if (!validateStep(nextStep)) return;
    setStep(nextStep);
  }

  function goBack() {
    setStep((current) => Math.max(current - 1, 0));
  }

  function handleSubmit(event: React.FormEvent) {
    event.preventDefault();

    if (!name.trim()) {
      setError("Estimate name is required.");
      setStep(0);
      return;
    }

    const normalizedDemandLines = demandLines
      .map((line, index) => {
        const resource = line.resourceId
          ? resources.find((item) => item.id === line.resourceId) ?? null
          : null;
        const role = line.roleId
          ? roles.find((item) => item.id === line.roleId) ?? null
          : null;
        const hours = toHours(line.hours);
        const costRateCents = toCents(line.costRate);
        const billRateCents = toCents(line.billRate);
        const displayName = line.name.trim() || resource?.displayName || role?.name || `Line ${index + 1}`;

        return {
          resourceId: line.resourceId ?? undefined,
          roleId: line.roleId ?? undefined,
          lineType: "LABOR",
          name: displayName,
          chapter: line.chapter || resource?.chapter || undefined,
          hours,
          days: hours > 0 ? Number((hours / 8).toFixed(2)) : undefined,
          rateSource: resource ? "RESOURCE" : role ? "ROLE" : "MANUAL",
          costRateCents,
          billRateCents,
          currency: line.currency || resource?.currency || baseCurrency,
          costTotalCents: Math.round(hours * costRateCents),
          priceTotalCents: Math.round(hours * billRateCents),
          monthlySpread:
            selectedProject?.startDate && selectedProject?.endDate && hours > 0
              ? computeEvenSpread({
                  totalHours: hours,
                  startDate: new Date(selectedProject.startDate),
                  endDate: new Date(selectedProject.endDate),
                }).spread
              : {},
          staffingAttributes: {
            linkedResource: resource ? true : false,
            linkedRole: role ? true : false,
          },
          metadata: {},
        };
      })
      .filter((line) => line.hours > 0);

    const normalizedScopeItems = scopeItems
      .map((item, index) => ({
        sequenceNo: index + 1,
        scopeType: item.scopeType.trim() || "SHOT",
        name: item.name.trim(),
        description: item.description.trim() || undefined,
        technicalSpec: {},
        sortOrder: index,
        metadata: {},
      }))
      .filter((item) => item.name.length > 0);

    const normalizedAssumptions = assumptions
      .map((assumption, index) => ({
        category: assumption.category.trim() || "general",
        key: assumption.key.trim() || slugify(assumption.label) || `assumption_${index + 1}`,
        label: assumption.label.trim(),
        valueType: "text",
        value: assumption.value.trim(),
        sortOrder: index,
      }))
      .filter((assumption) => assumption.label.length > 0 && String(assumption.value).length > 0);

    const seenResources = new Set<string>();
    const resourceSnapshots = normalizedDemandLines.flatMap((line) => {
      if (!line.resourceId) return [];
      if (seenResources.has(line.resourceId)) return [];
      seenResources.add(line.resourceId);

      const resource = resources.find((item) => item.id === line.resourceId) ?? null;
      if (!resource) return [];

      return [
        {
          resourceId: resource.id,
          sourceEid: resource.eid,
          displayName: resource.displayName,
          chapter: resource.chapter ?? undefined,
          roleId: resource.roleId ?? undefined,
          currency: resource.currency,
          lcrCents: resource.lcrCents,
          ucrCents: resource.ucrCents,
          location: resource.federalState ?? undefined,
          attributes: {},
        },
      ];
    });

    createMutation.mutate({
      projectId: projectId ?? undefined,
      name: name.trim(),
      opportunityId: opportunityId.trim() || undefined,
      baseCurrency,
      status,
      versionLabel: versionLabel.trim() || undefined,
      versionNotes: versionNotes.trim() || undefined,
      assumptions: normalizedAssumptions,
      scopeItems: normalizedScopeItems,
      demandLines: normalizedDemandLines,
      resourceSnapshots,
      metrics: [],
    });
  }

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-gray-950/45 p-4">
      <div ref={panelRef} className="flex max-h-[92vh] w-full max-w-6xl flex-col overflow-hidden rounded-[32px] bg-white shadow-2xl">
        <div className="border-b border-gray-100 px-6 py-5">
          <div className="flex items-start justify-between gap-4">
            <div>
              <p className="text-xs font-semibold uppercase tracking-[0.24em] text-brand-600">Estimate Wizard</p>
              <h2 className="mt-2 text-2xl font-semibold text-gray-900">Create a connected estimate</h2>
              <p className="mt-1 text-sm text-gray-500">
                Rates, resource snapshots, and project linkage are pulled from existing CapaKraken data.
              </p>
            </div>
            <button
              type="button"
              onClick={onClose}
              className="rounded-full border border-gray-200 px-3 py-2 text-sm text-gray-500 transition hover:border-gray-300 hover:text-gray-700"
            >
              Close
            </button>
          </div>

          <div className="mt-5 grid gap-2 md:grid-cols-5">
            {STEP_LABELS.map((label, index) => (
              <button
                key={label}
                type="button"
                onClick={() => {
                  if (index <= step || validateStep(index)) {
                    setStep(index);
                  }
                }}
                className={clsx(
                  "rounded-2xl px-4 py-3 text-left transition",
                  index === step
                    ? "bg-brand-600 text-white"
                    : index < step
                      ? "bg-brand-50 text-brand-700"
                      : "bg-gray-50 text-gray-400",
                )}
              >
                <span className="block text-xs uppercase tracking-wide">Step {index + 1}</span>
                <span className="mt-1 block text-sm font-semibold">{label}</span>
              </button>
            ))}
          </div>
        </div>

        <form onSubmit={handleSubmit} className="flex min-h-0 flex-1 flex-col">
          <div className="grid min-h-0 flex-1 gap-0 lg:grid-cols-[minmax(0,1.15fr),360px]">
            <div className="min-h-0 overflow-y-auto px-6 py-6">
              {step === 0 && (
                <div className="space-y-5">
                  <div className="grid gap-5 md:grid-cols-2">
                    <div>
                      <label className={LABEL_CLS}>Estimate Name <InfoTooltip content="A descriptive name for this estimate, e.g. project name + scope qualifier." /></label>
                      <input value={name} onChange={(event) => setName(event.target.value)} className={INPUT_CLS} placeholder="CGI Breakdown Q2 2026" />
                    </div>
                    <div>
                      <label className={LABEL_CLS}>Linked Project <InfoTooltip content="Link to an existing CapaKraken project. This enables automatic date-based phasing and planning handoff." /></label>
                      <ProjectCombobox value={projectId} onChange={setProjectId} placeholder="Link to project" />
                    </div>
                    <div>
                      <label className={LABEL_CLS}>Opportunity ID <InfoTooltip content="Optional external reference from your CRM or sales system to track this opportunity." /></label>
                      <input value={opportunityId} onChange={(event) => setOpportunityId(event.target.value)} className={INPUT_CLS} placeholder="Optional CRM or sales reference" />
                    </div>
                    <div>
                      <label className={LABEL_CLS}>Estimate Status <InfoTooltip content="DRAFT: work in progress. IN_REVIEW: submitted for approval. APPROVED: locked and ready for handoff. ARCHIVED: no longer active." /></label>
                      <select value={status} onChange={(event) => setStatus(event.target.value as EstimateStatus)} className={SELECT_CLS}>
                        {Object.values(EstimateStatus).map((value) => (
                          <option key={value} value={value}>
                            {value.replace("_", " ")}
                          </option>
                        ))}
                      </select>
                    </div>
                    <div>
                      <label className={LABEL_CLS}>Base Currency <InfoTooltip content="ISO 4217 currency code (e.g. EUR, USD) used for all monetary values in this estimate." /></label>
                      <input value={baseCurrency} onChange={(event) => setBaseCurrency(event.target.value.toUpperCase())} className={INPUT_CLS} maxLength={3} />
                    </div>
                    <div>
                      <label className={LABEL_CLS}>Version Label <InfoTooltip content="A label for the initial version snapshot. Use labels like 'Initial', 'Client revision 2', etc." /></label>
                      <input value={versionLabel} onChange={(event) => setVersionLabel(event.target.value)} className={INPUT_CLS} placeholder="Initial" />
                    </div>
                  </div>

                  <div>
                    <label className={LABEL_CLS}>Version Notes <InfoTooltip content="Free-text notes for this version. Document assumptions, exclusions, or client comments." /></label>
                    <textarea
                      value={versionNotes}
                      onChange={(event) => setVersionNotes(event.target.value)}
                      rows={5}
                      className={INPUT_CLS}
                      placeholder="Document assumptions, exclusions, or client comments."
                    />
                  </div>

                  <div className="rounded-3xl border border-gray-100 bg-gray-50 p-5">
                    <p className="text-sm font-semibold text-gray-900">Live connection preview</p>
                    <div className="mt-4 grid gap-3 md:grid-cols-2">
                      <div className="rounded-2xl bg-white px-4 py-3">
                        <p className="text-xs uppercase tracking-wide text-gray-400">Project source</p>
                        <p className="mt-1 text-sm text-gray-700">
                          {selectedProject ? `${selectedProject.shortCode} - ${selectedProject.name}` : "Not linked yet"}
                        </p>
                      </div>
                      <div className="rounded-2xl bg-white px-4 py-3">
                        <p className="text-xs uppercase tracking-wide text-gray-400">Live catalogs</p>
                        <p className="mt-1 text-sm text-gray-700">
                          {roles.length} roles, {resources.length} active resources available
                        </p>
                      </div>
                    </div>
                  </div>
                </div>
              )}

              {step === 1 && (
                <div className="space-y-4">
                  <div className="flex items-center justify-between">
                    <div>
                      <h3 className="text-lg font-semibold text-gray-900">Commercial and delivery assumptions <InfoTooltip content="Preconditions that affect the estimate validity. If an assumption changes, the estimate may need revision." /></h3>
                      <p className="text-sm text-gray-500">These rows replace free-form spreadsheet notes with structured data.</p>
                    </div>
                    <button type="button" onClick={() => setAssumptions((current) => [...current, makeAssumption()])} className="rounded-xl border border-gray-200 px-3 py-2 text-sm text-gray-600 transition hover:border-gray-300 hover:text-gray-900">
                      Add assumption
                    </button>
                  </div>

                  <div className="space-y-3">
                    {assumptions.map((row) => (
                      <div key={row.id} className="grid gap-3 rounded-2xl border border-gray-100 p-4 md:grid-cols-[140px,1fr,1fr,1.2fr,auto]">
                        <input value={row.category} onChange={(event) => updateAssumption(row.id, { category: event.target.value })} className={INPUT_CLS} placeholder="Category" />
                        <input value={row.label} onChange={(event) => updateAssumption(row.id, { label: event.target.value })} className={INPUT_CLS} placeholder="Label" />
                        <input value={row.key} onChange={(event) => updateAssumption(row.id, { key: event.target.value })} className={INPUT_CLS} placeholder="Key (optional)" />
                        <input value={row.value} onChange={(event) => updateAssumption(row.id, { value: event.target.value })} className={INPUT_CLS} placeholder="Value" />
                        <button type="button" onClick={() => setAssumptions((current) => current.filter((item) => item.id !== row.id))} className="rounded-xl border border-transparent px-3 py-2 text-sm text-red-500 transition hover:border-red-100 hover:bg-red-50">
                          Remove
                        </button>
                      </div>
                    ))}
                  </div>
                </div>
              )}

              {step === 2 && (
                <div className="space-y-4">
                  <div className="flex items-center justify-between">
                    <div>
                      <h3 className="text-lg font-semibold text-gray-900">Scope breakdown <InfoTooltip content="Deliverables and work packages that define what is included in this estimate." /></h3>
                      <p className="text-sm text-gray-500">Create structured work packages that can later evolve into versioned estimate scope.</p>
                    </div>
                    <div className="flex gap-2">
                      <label className="cursor-pointer rounded-xl border border-gray-200 px-3 py-2 text-sm text-gray-600 transition hover:border-gray-300 hover:text-gray-900">
                        Import XLSX
                        <input type="file" accept=".xlsx,.xls,.csv" onChange={handleScopeImport} className="hidden" />
                      </label>
                      <button type="button" onClick={() => setScopeItems((current) => [...current, makeScope(current.length + 1)])} className="rounded-xl border border-gray-200 px-3 py-2 text-sm text-gray-600 transition hover:border-gray-300 hover:text-gray-900">
                        Add scope row
                      </button>
                    </div>
                  </div>

                  {scopeImportWarnings.length > 0 && (
                    <div className="rounded-2xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-700">
                      {scopeImportWarnings.map((warning, index) => (
                        <p key={index}>{warning}</p>
                      ))}
                    </div>
                  )}

                  <div className="space-y-3">
                    {scopeItems.map((item, index) => (
                      <div key={item.id} className="grid gap-3 rounded-2xl border border-gray-100 p-4 md:grid-cols-[90px,120px,1fr,1.2fr,auto]">
                        <input value={String(index + 1)} readOnly className={clsx(INPUT_CLS, "bg-gray-50 text-gray-500")} />
                        <input value={item.scopeType} onChange={(event) => updateScopeItem(item.id, { scopeType: event.target.value })} className={INPUT_CLS} placeholder="Type" />
                        <input value={item.name} onChange={(event) => updateScopeItem(item.id, { name: event.target.value })} className={INPUT_CLS} placeholder="Name" />
                        <input value={item.description} onChange={(event) => updateScopeItem(item.id, { description: event.target.value })} className={INPUT_CLS} placeholder="Description" />
                        <button type="button" onClick={() => setScopeItems((current) => current.filter((row) => row.id !== item.id))} className="rounded-xl border border-transparent px-3 py-2 text-sm text-red-500 transition hover:border-red-100 hover:bg-red-50">
                          Remove
                        </button>
                      </div>
                    ))}
                  </div>
                </div>
              )}

              {step === 3 && (
                <div className="space-y-4">
                  <div className="flex items-center justify-between">
                    <div>
                      <h3 className="text-lg font-semibold text-gray-900">Staffing and rate lines <InfoTooltip content="Each line represents a staffing need. Line cost = hours x cost rate. Line price = hours x sell rate." /></h3>
                      <p className="text-sm text-gray-500">Selecting a resource pre-fills cost rate, sell rate, chapter, and role from live data.</p>
                    </div>
                    <button type="button" onClick={() => setDemandLines((current) => [...current, makeDemand()])} className="rounded-xl border border-gray-200 px-3 py-2 text-sm text-gray-600 transition hover:border-gray-300 hover:text-gray-900">
                      Add staffing line
                    </button>
                  </div>

                  <div className="space-y-4">
                    {demandLines.map((line) => {
                      const resource = line.resourceId
                        ? resources.find((item) => item.id === line.resourceId) ?? null
                        : null;

                      return (
                        <div key={line.id} className="rounded-3xl border border-gray-100 p-4">
                          <div className="grid gap-4 lg:grid-cols-2">
                            <div>
                              <label className={LABEL_CLS}>Resource <InfoTooltip content="Link to a live CapaKraken resource. Auto-fills rates, chapter, and role." /></label>
                              <ResourceCombobox value={line.resourceId} onChange={(resourceId) => applyResource(resourceId, line.id)} placeholder="Search resource" />
                            </div>
                            <div>
                              <label className={LABEL_CLS}>Role <InfoTooltip content="The production role for this demand line (e.g. Compositor, Animator)." /></label>
                              <select value={line.roleId ?? ""} onChange={(event) => updateDemandLine(line.id, { roleId: event.target.value || null })} className={SELECT_CLS}>
                                <option value="">Unassigned</option>
                                {roles.map((role) => (
                                  <option key={role.id} value={role.id}>
                                    {role.name}
                                  </option>
                                ))}
                              </select>
                            </div>
                            <div>
                              <label className={LABEL_CLS}>Line Name <InfoTooltip content="Descriptive name for this staffing line, e.g. 'Compositing Lead' or 'PM overhead'." /></label>
                              <input value={line.name} onChange={(event) => updateDemandLine(line.id, { name: event.target.value })} className={INPUT_CLS} placeholder="Compositing, lighting, PM, ..." />
                            </div>
                            <div>
                              <label className={LABEL_CLS}>Chapter <InfoTooltip content="Department or cost center. Auto-filled when a resource is linked." /></label>
                              <input value={line.chapter} onChange={(event) => updateDemandLine(line.id, { chapter: event.target.value })} className={INPUT_CLS} placeholder="Auto-filled from resource when linked" />
                            </div>
                            <div>
                              <label className={LABEL_CLS}>Hours <InfoTooltip content="Total estimated effort in hours. Used to calculate line cost and price." /></label>
                              <input value={line.hours} onChange={(event) => updateDemandLine(line.id, { hours: event.target.value })} className={INPUT_CLS} inputMode="decimal" />
                            </div>
                            <div>
                              <label className={LABEL_CLS}>Currency <InfoTooltip content="ISO 4217 currency code for this line's rates." /></label>
                              <input value={line.currency} onChange={(event) => updateDemandLine(line.id, { currency: event.target.value.toUpperCase() })} className={INPUT_CLS} maxLength={3} />
                            </div>
                            <div>
                              <label className={LABEL_CLS}>Cost Rate / h <InfoTooltip content="Internal hourly cost rate in EUR. Line cost = hours x cost rate." /></label>
                              <input value={line.costRate} onChange={(event) => updateDemandLine(line.id, { costRate: event.target.value })} className={INPUT_CLS} inputMode="decimal" />
                            </div>
                            <div>
                              <label className={LABEL_CLS}>Sell Rate / h <InfoTooltip content="Client-facing hourly rate. Line price = hours x sell rate." /></label>
                              <input value={line.billRate} onChange={(event) => updateDemandLine(line.id, { billRate: event.target.value })} className={INPUT_CLS} inputMode="decimal" />
                            </div>
                          </div>

                          <div className="mt-4 flex flex-wrap items-center justify-between gap-3 rounded-2xl bg-gray-50 px-4 py-3">
                            <div className="text-sm text-gray-600">
                              {resource ? `Linked to ${resource.displayName} (${resource.eid})` : "Manual line"}
                            </div>
                            <div className="flex flex-wrap gap-4 text-sm">
                              <span className="font-medium text-gray-700">
                                Cost {formatMoney(Math.round(toHours(line.hours) * toCents(line.costRate)), line.currency)}
                              </span>
                              <span className="font-medium text-gray-700">
                                Price {formatMoney(Math.round(toHours(line.hours) * toCents(line.billRate)), line.currency)}
                              </span>
                            </div>
                            <button type="button" onClick={() => setDemandLines((current) => current.filter((item) => item.id !== line.id))} className="rounded-xl border border-transparent px-3 py-2 text-sm text-red-500 transition hover:border-red-100 hover:bg-red-50">
                              Remove
                            </button>
                          </div>
                        </div>
                      );
                    })}
                  </div>
                </div>
              )}

              {step === 4 && (
                <div className="space-y-6">
                  <div>
                    <h3 className="text-lg font-semibold text-gray-900">Review</h3>
                    <p className="text-sm text-gray-500">The summary metrics below are recalculated from the demand rows and persisted on create.</p>
                  </div>

                  <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
                    <div className="rounded-3xl border border-gray-100 bg-gray-50 px-5 py-4">
                      <p className="text-xs uppercase tracking-wide text-gray-400">Total Hours <InfoTooltip content="Sum of all demand line hours across the estimate." /></p>
                      <p className="mt-2 text-2xl font-semibold text-gray-900">{summary.totalHours.toFixed(1)}</p>
                    </div>
                    <div className="rounded-3xl border border-gray-100 bg-gray-50 px-5 py-4">
                      <p className="text-xs uppercase tracking-wide text-gray-400">Total Cost <InfoTooltip content="Sum of (hours x cost rate) for each demand line. Stored in cents, displayed in EUR." /></p>
                      <p className="mt-2 text-2xl font-semibold text-gray-900">{formatMoney(summary.totalCostCents, baseCurrency)}</p>
                    </div>
                    <div className="rounded-3xl border border-gray-100 bg-gray-50 px-5 py-4">
                      <p className="text-xs uppercase tracking-wide text-gray-400">Total Price <InfoTooltip content="Sum of (hours x sell rate) for each demand line. This is the client-facing revenue." /></p>
                      <p className="mt-2 text-2xl font-semibold text-gray-900">{formatMoney(summary.totalPriceCents, baseCurrency)}</p>
                    </div>
                    <div className="rounded-3xl border border-gray-100 bg-gray-50 px-5 py-4">
                      <p className="text-xs uppercase tracking-wide text-gray-400">Margin <InfoTooltip content="Margin = Total Price - Total Cost. Margin % = Margin / Total Price x 100." /></p>
                      <p className="mt-2 text-2xl font-semibold text-gray-900">
                        {formatMoney(marginCents, baseCurrency)} ({marginPercent}%)
                      </p>
                    </div>
                  </div>

                  <div className="grid gap-5 lg:grid-cols-2">
                    <div className="rounded-3xl border border-gray-100 p-5">
                      <p className="text-sm font-semibold text-gray-900">Estimate envelope</p>
                      <dl className="mt-4 space-y-2 text-sm text-gray-600">
                        <div className="flex justify-between gap-4">
                          <dt>Name</dt>
                          <dd className="text-right text-gray-900">{name || "Untitled"}</dd>
                        </div>
                        <div className="flex justify-between gap-4">
                          <dt>Project</dt>
                          <dd className="text-right text-gray-900">{selectedProject ? `${selectedProject.shortCode}` : "Standalone"}</dd>
                        </div>
                        <div className="flex justify-between gap-4">
                          <dt>Status</dt>
                          <dd className="text-right text-gray-900">{status.replace("_", " ")}</dd>
                        </div>
                        <div className="flex justify-between gap-4">
                          <dt>Version</dt>
                          <dd className="text-right text-gray-900">{versionLabel || "Initial"}</dd>
                        </div>
                      </dl>
                    </div>

                    <div className="rounded-3xl border border-gray-100 p-5">
                      <p className="text-sm font-semibold text-gray-900">Connected records</p>
                      <dl className="mt-4 space-y-2 text-sm text-gray-600">
                        <div className="flex justify-between gap-4">
                          <dt>Assumptions</dt>
                          <dd className="text-right text-gray-900">{assumptions.filter((row) => row.label.trim()).length}</dd>
                        </div>
                        <div className="flex justify-between gap-4">
                          <dt>Scope items</dt>
                          <dd className="text-right text-gray-900">{scopeItems.filter((row) => row.name.trim()).length}</dd>
                        </div>
                        <div className="flex justify-between gap-4">
                          <dt>Demand lines</dt>
                          <dd className="text-right text-gray-900">{demandLines.filter((row) => toHours(row.hours) > 0).length}</dd>
                        </div>
                        <div className="flex justify-between gap-4">
                          <dt>Resource snapshots</dt>
                          <dd className="text-right text-gray-900">{new Set(demandLines.map((row) => row.resourceId).filter(Boolean)).size}</dd>
                        </div>
                      </dl>
                    </div>
                  </div>
                </div>
              )}
            </div>

            <aside className="border-t border-gray-100 bg-gray-50 px-6 py-6 lg:border-l lg:border-t-0">
              <p className="text-xs font-semibold uppercase tracking-[0.24em] text-gray-500">Dynamic summary</p>
              <div className="mt-4 space-y-3">
                <div className="rounded-2xl bg-white px-4 py-3">
                  <p className="text-xs uppercase tracking-wide text-gray-400">Project link</p>
                  <p className="mt-1 text-sm text-gray-800">
                    {selectedProject ? `${selectedProject.shortCode} - ${selectedProject.name}` : "No linked project"}
                  </p>
                </div>
                <div className="rounded-2xl bg-white px-4 py-3">
                  <p className="text-xs uppercase tracking-wide text-gray-400">Resource-linked demand</p>
                  <p className="mt-1 text-sm text-gray-800">
                    {demandLines.filter((line) => line.resourceId).length} of {demandLines.length} rows tied to live resources
                  </p>
                </div>
                <div className="rounded-2xl bg-white px-4 py-3">
                  <p className="text-xs uppercase tracking-wide text-gray-400">Calculated totals</p>
                  <p className="mt-1 text-sm text-gray-800">{summary.totalHours.toFixed(1)} h</p>
                  <p className="mt-1 text-sm text-gray-800">{formatMoney(summary.totalCostCents, baseCurrency)} cost</p>
                  <p className="mt-1 text-sm text-gray-800">{formatMoney(summary.totalPriceCents, baseCurrency)} price</p>
                </div>
              </div>

              {error && (
                <div className="mt-4 rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
                  {error}
                </div>
              )}
            </aside>
          </div>

          <div className="flex items-center justify-between border-t border-gray-100 px-6 py-4">
            <button type="button" onClick={step === 0 ? onClose : goBack} className="rounded-xl border border-gray-200 px-4 py-2 text-sm font-medium text-gray-600 transition hover:border-gray-300 hover:text-gray-900">
              {step === 0 ? "Cancel" : "Back"}
            </button>
            {step < STEP_LABELS.length - 1 ? (
              <button type="button" onClick={goNext} className="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-brand-700">
                Next
              </button>
            ) : (
              <button type="submit" disabled={createMutation.isPending} className="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-brand-700 disabled:cursor-not-allowed disabled:opacity-60">
                {createMutation.isPending ? "Creating..." : "Create Estimate"}
              </button>
            )}
          </div>
        </form>
      </div>
    </div>
  );
}
