/**
 * Growth — which account to expand, renew or protect next.
 *
 * The page used to announce itself: an accent pill reading "GROWTH
 * INTELLIGENCE" under a Sparkles glyph, a 30px headline, a blurred accent wash
 * and a gradient hairline on every customer card. None of that is in the rest
 * of the product, and two of the marks — the accent colour and the sparkle —
 * are the agent's, on a page the agent does not appear on. What is left is the
 * same header, tiles and badges as every other analysis surface, so the page
 * reads as part of one product rather than as its own launch.
 */
import { useMemo, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import type { CustomerLifecycleProjection } from '@pig/core';
import { AlertTriangle, ArrowUpRight, Gauge, Server } from 'lucide-react';
import { Link } from 'react-router-dom';
import { PiggyAskButton } from '@/components/PiggyChat';
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, EmptyState, Label, Skeleton, Stat } from '@/components/ui';
import { PageHeader } from '@/components/ui/page-header';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { FacetBadge, RelationshipBadge } from '@/components/status';
import { compactNumber, dateRange, get, money, unitPrice } from '@/lib/api';
import { initials } from '@/lib/utils';
import { usePageTitle } from '@/lib/title';

interface GrowthCustomer {
  account: { id: string; name: string; domain: string | null; customerSegment: string | null; ownerUserId: string | null };
  lifecycle: CustomerLifecycleProjection;
  openDealCount: number;
}

interface GrowthReport {
  rulesetVersion: string;
  computedAt: string;
  customers: GrowthCustomer[];
  customersTruncated: boolean;
  idleSupply: Array<{
    commitmentId: string;
    name: string;
    gpuType: string;
    gpuCount: number;
    startsAt: string;
    endsAt: string;
    soldGpuHours: number;
    heldGpuHours: number;
    availableGpuHours: number;
    idleGpuHours: number;
    idleCostCents: number;
    breakEvenPriceCents: number | null;
  }>;
}

type GrowthView = 'priority' | 'expansion' | 'renewal' | 'risk' | 'idle';

const GROWTH_VIEWS = [
  ['priority', 'Priority'],
  ['expansion', 'Expansion'],
  ['renewal', 'Renewal'],
  ['risk', 'Risk'],
  ['idle', 'Idle supply'],
] as const satisfies ReadonlyArray<readonly [GrowthView, string]>;

function customersInView(customers: GrowthCustomer[], view: GrowthView): GrowthCustomer[] {
  if (view === 'expansion') return customers.filter((row) => row.lifecycle.facets.includes('expansion_candidate'));
  if (view === 'renewal') return customers.filter((row) => row.lifecycle.facets.includes('renewal_due'));
  if (view === 'risk') return customers.filter((row) => row.lifecycle.facets.includes('at_risk') || row.lifecycle.facets.includes('data_stale'));
  return customers;
}

export function Growth() {
  usePageTitle('Growth');
  const [view, setView] = useState<GrowthView>('priority');
  const { data, isLoading, error, refetch } = useQuery({
    queryKey: ['growth'],
    queryFn: () => get<GrowthReport>('/api/growth'),
  });
  const totals = useMemo(() => {
    if (!data) return null;
    return {
      deployed: data.customers.filter((row) => row.lifecycle.relationshipState === 'deployed').length,
      expansion: data.customers.filter((row) => row.lifecycle.facets.includes('expansion_candidate')).length,
      attention: data.customers.filter((row) => row.lifecycle.facets.includes('renewal_due') || row.lifecycle.facets.includes('at_risk')).length,
      idleCost: data.idleSupply.reduce((sum, row) => sum + row.idleCostCents, 0),
    };
  }, [data]);

  if (isLoading) {
    return (
      <div className="space-y-6">
        <Skeleton className="h-16" />
        <div className="grid grid-cols-2 gap-3 xl:grid-cols-4">
          {Array.from({ length: 4 }).map((_, index) => <Skeleton key={index} className="h-28" />)}
        </div>
        <Skeleton className="h-80" />
      </div>
    );
  }
  if (error || !data || !totals) {
    return (
      <Card>
        <CardContent className="pt-5">
          <EmptyState
            title="Growth is unavailable"
            description={error instanceof Error ? error.message : 'The lifecycle projection could not be loaded.'}
            action={<Button variant="outline" onClick={() => void refetch()}>Try again</Button>}
          />
        </CardContent>
      </Card>
    );
  }

  return (
    <div className="space-y-6 pb-[calc(5.5rem+var(--safe-bottom))] md:pb-0">
      <PageHeader
        title="Growth"
        description={
          <>
            Expand, renew, or protect the right account next. Deterministic signals from customer
            paper, deal activity and sold or reserved capacity — an attention score is not a win or
            churn probability.
            {/* Provenance sits with the claim rather than on a settings page:
                a ranking nobody can date is a ranking nobody can argue with. */}
            <span className="mt-1 block text-xs">
              Rules {data.rulesetVersion} · computed {new Date(data.computedAt).toLocaleString()}
            </span>
          </>
        }
      />

      {data.customersTruncated ? (
        <div className="rounded-xl border border-warning/30 bg-warning/10 px-4 py-3 text-sm text-warning" role="status">
          Showing the 200 most recently updated demand accounts. Narrow the workspace before treating these totals as complete.
        </div>
      ) : null}

      {/*
        One coloured figure. Renewal or risk is the only one of the four that
        names something a person has to decide this week; a count of deployed
        customers is a fact, and the idle cost below it is the same money the
        Overview already flags at a lower threshold — drawn in danger here it
        made the page's own subject, the accounts, the third-loudest thing on it.
      */}
      <section className="grid grid-cols-2 gap-3 xl:grid-cols-4">
        <Stat label="Deployed customers" value={totals.deployed} hint="Active sold capacity" />
        <Stat label="Expansion candidates" value={totals.expansion} hint="Evidence-backed openings" />
        <Stat label="Renewal or risk" value={totals.attention} hint="Needs a human decision" tone={totals.attention ? 'warning' : 'default'} />
        {/*
          Scoped in the hint, because this is the cost of the blocks listed
          under "Idle supply" and not the book's whole idle spend. Read as a
          total it contradicts the Overview, which draws its idle exposure at a
          lower threshold and therefore always shows a larger number for the
          same book.
        */}
        <Stat label="Idle supply cost" value={money(totals.idleCost)} hint="Near-term blocks over the idle threshold" />
      </section>

      {/*
        Radix rather than the five hand-rolled buttons that were here: they
        announced themselves as a tablist without a tabpanel to point at, and
        the selected one wore a shadow, which in this system belongs to cards
        alone.
      */}
      <Tabs value={view} onValueChange={(next) => setView(next as GrowthView)}>
        {/* The list is wider than a phone at five tabs, so it scrolls in its
            own gutter rather than making the page scroll sideways. */}
        <div className="scroll-x -mx-1 px-1 pb-1">
          <TabsList aria-label="Growth views">
            {GROWTH_VIEWS.map(([value, label]) => (
              <TabsTrigger key={value} value={value}>{label}</TabsTrigger>
            ))}
          </TabsList>
        </div>
        {GROWTH_VIEWS.map(([value]) => (
          <TabsContent key={value} value={value}>
            {value === 'idle' ? (
              <IdleSupply rows={data.idleSupply} />
            ) : (
              <CustomerGrid customers={customersInView(data.customers, value)} />
            )}
          </TabsContent>
        ))}
      </Tabs>
    </div>
  );
}

function CustomerGrid({ customers }: { customers: GrowthCustomer[] }) {
  if (!customers.length) {
    return (
      <Card>
        <EmptyState
          icon={<Gauge className="size-8" />}
          title="No accounts in this view"
          description="Growth only surfaces a facet when its deterministic evidence threshold is met."
        />
      </Card>
    );
  }
  return (
    // `items-start`, as the Overview grid does: an account with one signal
    // stretched to the height of one with four, opening a hole under its own
    // buttons.
    <section className="grid items-start gap-3 xl:grid-cols-2">
      {customers.map((customer) => <CustomerCard key={customer.account.id} customer={customer} />)}
    </section>
  );
}

function CustomerCard({ customer }: { customer: GrowthCustomer }) {
  const { account, lifecycle } = customer;
  return (
    <Card>
      <CardHeader className="gap-3">
        <div className="flex items-start gap-3">
          <div className="flex size-11 shrink-0 items-center justify-center rounded-xl bg-accent-subtle font-semibold text-accent-fg">{initials(account.name)}</div>
          <div className="min-w-0 flex-1">
            <CardTitle className="break-words text-base leading-snug">
              <Link className="underline-offset-4 hover:underline" to={`/accounts/${account.id}`}>{account.name}</Link>
            </CardTitle>
            <p className="mt-1 truncate text-xs text-muted">{account.domain ?? account.customerSegment?.replaceAll('_', ' ') ?? 'Demand account'}</p>
          </div>
          {/* A panel figure, not a page one: the account's name outranks its
              score, and at 30px the score was the largest thing on the card. */}
          <Stat surface="bare" size="md" label="Attention" value={lifecycle.score} className="shrink-0 text-right" />
        </div>
        <div className="flex flex-wrap gap-1.5"><RelationshipBadge state={lifecycle.relationshipState} />{lifecycle.facets.map((facet) => <FacetBadge key={facet} facet={facet} />)}</div>
      </CardHeader>
      <CardContent className="flex flex-col gap-3">
        <div className="grid grid-cols-3 gap-2">
          <Stat surface="inset" size="sm" label="Open deals" value={customer.openDealCount} />
          <Stat surface="inset" size="sm" label="Sold capacity" value={compactNumber(lifecycle.soldCapacityGpuHours)} />
          <Stat surface="inset" size="sm" label="Held capacity" value={compactNumber(lifecycle.heldCapacityGpuHours)} />
        </div>
        <div className="space-y-2">
          {lifecycle.signals.slice(0, 2).map((signal) => (
            <div key={`${signal.code}:${signal.sourceRefs.map((ref) => ref.id).join(':')}`} className="flex gap-3 rounded-lg bg-surface-2 p-3">
              {/* `bg-surface` on the tile's own `bg-surface-2`: the weight chip
                  used to be surface-2 inside a bordered box and disappeared the
                  moment the box became a tile. */}
              <span className="nums flex size-8 shrink-0 items-center justify-center rounded-md bg-surface text-xs font-semibold">+{signal.weight}</span>
              <div className="min-w-0">
                <p className="text-sm leading-5">{signal.explanation}</p>
                <Label className="mt-1">{signal.category} · {signal.sourceRefs.map((ref) => ref.type.replaceAll('_', ' ')).join(', ')}</Label>
              </div>
            </div>
          ))}
          {lifecycle.signals.length > 2 ? <p className="px-1 text-xs text-muted">+{lifecycle.signals.length - 2} more evidence signal{lifecycle.signals.length === 3 ? '' : 's'} in the account context</p> : null}
        </div>
        {/* 8px, the inset-tile radius. `Account.tsx` renders the same blocker
            string at `rounded-md` and this rendered it at `rounded-lg` — the
            same sentence at two radii on two pages was the clearest single
            proof left that the radius rule was not being followed. */}
        {lifecycle.blockers.length ? <div className="rounded-md bg-warning/10 p-3 text-sm text-warning"><div className="flex gap-2"><AlertTriangle className="mt-0.5 size-4 shrink-0" aria-hidden /><span>{lifecycle.blockers[0]}</span></div></div> : null}
        <div className="flex flex-wrap gap-2">
          <PiggyAskButton context={{ type: 'account', id: account.id, label: account.name }} prompt="Explain this account's lifecycle score and the highest-value next review. Distinguish facts from inference." label="Ask Piggy" variant="outline" />
          <Link className="tap inline-flex min-h-11 flex-1 items-center justify-center gap-2 rounded-lg px-3 text-sm font-medium hover:bg-surface-2 sm:flex-none" to={`/accounts/${account.id}`}>Open account <ArrowUpRight className="size-4" aria-hidden /></Link>
        </div>
      </CardContent>
    </Card>
  );
}

function IdleSupply({ rows }: { rows: GrowthReport['idleSupply'] }) {
  if (!rows.length) return <Card><EmptyState icon={<Server className="size-8" />} title="No material idle supply" description="No near-term commitment currently clears the idle-capacity threshold." /></Card>;
  return <section className="grid items-start gap-3 md:grid-cols-2 xl:grid-cols-3">{rows.map((row) => (
    <Card key={row.commitmentId}>
      <CardHeader>
        <div className="flex items-start justify-between gap-3">
          <div className="min-w-0">
            <CardTitle className="break-words text-base leading-snug">{row.name}</CardTitle>
            <p className="mt-1 text-sm text-muted">{row.gpuCount}× {row.gpuType}</p>
          </div>
          <Badge tone="warning" className="nums shrink-0">{money(row.idleCostCents)} idle</Badge>
        </div>
      </CardHeader>
      <CardContent className="space-y-3">
        <div className="grid grid-cols-3 gap-2">
          <Stat surface="inset" size="sm" label="Sold" value={compactNumber(row.soldGpuHours)} />
          <Stat surface="inset" size="sm" label="Held" value={compactNumber(row.heldGpuHours)} />
          <Stat surface="inset" size="sm" label="Sellable" value={compactNumber(row.availableGpuHours)} />
        </div>
        <div className="space-y-2 text-sm"><p className="flex justify-between gap-3"><span className="text-muted">Window</span><span className="text-right">{dateRange(row.startsAt, row.endsAt)}</span></p><p className="flex justify-between gap-3"><span className="text-muted">Break even</span><span className="nums text-right">{row.breakEvenPriceCents == null ? 'Sold out' : row.breakEvenPriceCents === 0 ? 'Cost covered' : `${unitPrice(row.breakEvenPriceCents)}/GPU-hr`}</span></p></div>
        <Link className="tap inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-lg border border-border px-4 text-sm font-medium hover:bg-surface-2" to="/capacity">Match this capacity <ArrowUpRight className="size-4" aria-hidden /></Link>
      </CardContent>
    </Card>
  ))}</section>;
}
