/**
 * Every status a PIG record can be in, drawn once.
 *
 * The audit found this file's contents scattered across five others: account
 * side and relationship state in `Account.tsx` and again in `Growth.tsx` with
 * different tones for the same value, contract status in `Contracts.tsx` and
 * again in `Account.tsx` disagreeing about whether "expired" is red, run and
 * task state in `activity-panel.tsx`, renewal urgency in `Contracts.tsx`. Two
 * implementations of the same badge do not merely duplicate; they contradict,
 * and a reader who learns that green means good on one page and nothing on the
 * next has learned that colour here is decoration.
 *
 * So the tones follow one rule, taken from the design direction, and the rule
 * is stricter than what any of the five did:
 *
 *   positive  a business figure that is good — capacity actually running, a
 *             deal won, an expansion open
 *   warning   something needs a person NOW — a notice window already open
 *   danger    a loss, or a failure a person must resolve — an account gone, a
 *             run that failed, an authorisation that has lapsed
 *   info      a neutral time or system fact — running, queued, out for signature
 *   neutral   done, fine, nothing to do
 *
 * The consequence, and it is the point: **process outcomes get no colour.**
 * "Executed", "Succeeded", "Closed lost", "Terminated" are all neutral. A
 * ledger where every success is green is a ledger nobody scans, and the one
 * row that needs a person is invisible in a column of colour.
 *
 * Colour is never the only signal. Every badge here carries a word, and the
 * states that need a person or record a failure carry a mark as well, so the
 * scan works without hue: a **circle** for "act on this", a **triangle** for
 * "this went wrong". Two different shapes, not two colours of the same one —
 * lucide's `AlertTriangle` is an alias of `TriangleAlert`, so the obvious pair
 * drew the identical glyph twice and the distinction existed only in the hue it
 * was supposed to be independent of. Nothing else gets an icon: a badge set
 * where every chip has a glyph is a badge set with no emphasis left to spend.
 */
import { CircleAlert, TriangleAlert } from 'lucide-react';
import {
  DEMAND_STAGE_LABELS,
  SUPPLY_STAGE_LABELS,
  type AccountSide,
  type ContractStatus,
  type CustomerRelationshipState,
  type DemandStage,
  type GrowthFacet,
  type SupplyStage,
} from '@pig/core';
import { Badge, cn } from '@/components/ui';
import { shortDate } from '@/lib/api';

/** The tones `Badge` understands. `accent` is deliberately absent: the accent
 *  is PIG's identity colour and never carries meaning. */
export type StatusTone = 'neutral' | 'positive' | 'warning' | 'danger' | 'info';

/**
 * The two marks, sized to the 12px badge text.
 *
 * Lucide ships icons at 24px and `Badge` does not size its children, so an
 * unsized glyph in a badge renders twice the height of the word beside it —
 * which is what `Contracts.tsx` and `Growth.tsx` were both doing.
 */
function ActMark() {
  return <CircleAlert className="size-3.5" aria-hidden />;
}

function FailMark() {
  return <TriangleAlert className="size-3.5" aria-hidden />;
}

/** Underscored enum value to a readable word, for the values with no label map. */
function humanise(value: string): string {
  const words = value.replaceAll('_', ' ').trim();
  return words ? `${words.charAt(0).toUpperCase()}${words.slice(1)}` : value;
}

// --------------------------------------------------------------- account side

/**
 * Which side of the book an account sits on.
 *
 * All three are neutral. Side is identity, not status — nothing about being a
 * supplier is good or bad or needs anybody — and the words already tell them
 * apart. `Account.tsx` and `Accounts.tsx` were both painting supply blue and
 * both sides accent, which spent two of the five tones on a fact that changes
 * nothing a reader would do.
 */
export const SIDE_TONES: Record<AccountSide, StatusTone> = {
  supply: 'neutral',
  demand: 'neutral',
  both: 'neutral',
};

export const SIDE_LABELS: Record<AccountSide, string> = {
  supply: 'Buy-side',
  demand: 'Sell-side',
  both: 'Both sides',
};

export function SideBadge({ side, className }: { side: AccountSide; className?: string }) {
  return (
    <Badge tone={SIDE_TONES[side]} className={cn('shrink-0', className)}>
      {SIDE_LABELS[side]}
    </Badge>
  );
}

// -------------------------------------------------------- customer lifecycle

/**
 * Where a customer relationship stands.
 *
 * `deployed` is the only good one: capacity is actually running, which is the
 * figure the business is built on. `former_customer` is a loss and says so.
 * `prospect` and `contracted` are stages on the way, and a stage is not news.
 */
export const RELATIONSHIP_TONES: Record<CustomerRelationshipState, StatusTone> = {
  prospect: 'neutral',
  contracted: 'neutral',
  deployed: 'positive',
  former_customer: 'danger',
};

export const RELATIONSHIP_LABELS: Record<CustomerRelationshipState, string> = {
  prospect: 'Prospect',
  contracted: 'Contracted',
  deployed: 'Deployed',
  former_customer: 'Former customer',
};

export function RelationshipBadge({
  state,
  className,
}: {
  state: CustomerRelationshipState;
  className?: string;
}) {
  return (
    <Badge tone={RELATIONSHIP_TONES[state]} className={className}>
      {RELATIONSHIP_LABELS[state]}
    </Badge>
  );
}

// ---------------------------------------------------------------- growth facet

/**
 * Why an account is on the Growth page.
 *
 * The old version coloured all six, which made the page a mosaic. Here a
 * deadline and a risk are the only things that get a person's attention, and
 * the hygiene facets — a coverage gap, stale data — are grey. Stale data was
 * `warning` before, competing for the eye with `at_risk` on the same row.
 */
export const FACET_TONES: Record<GrowthFacet, StatusTone> = {
  expansion_candidate: 'positive',
  renewal_due: 'warning',
  at_risk: 'danger',
  idle_supply_match: 'info',
  coverage_gap: 'neutral',
  data_stale: 'neutral',
};

export const FACET_LABELS: Record<GrowthFacet, string> = {
  expansion_candidate: 'Expansion candidate',
  renewal_due: 'Renewal due',
  at_risk: 'At risk',
  idle_supply_match: 'Idle supply match',
  coverage_gap: 'Coverage gap',
  data_stale: 'Data stale',
};

export function FacetBadge({ facet, className }: { facet: GrowthFacet; className?: string }) {
  return (
    <Badge tone={FACET_TONES[facet]} className={className}>
      {facet === 'renewal_due' ? <ActMark /> : facet === 'at_risk' ? <FailMark /> : null}
      {FACET_LABELS[facet]}
    </Badge>
  );
}

// -------------------------------------------------------------------- deals

/**
 * Pipeline stage, both sides.
 *
 * Won and live are business-good; everything in flight is process and stays
 * grey, which is the change from the accent-coloured pipeline the pages drew
 * before. `churned` is red where `closed_lost` is not: losing a relationship
 * is a loss worth marking, losing one deal out of a pipeline of them is the
 * ordinary shape of the job.
 */
export const DEMAND_STAGE_TONES: Record<DemandStage, StatusTone> = {
  qualification: 'neutral',
  legal: 'neutral',
  scoping: 'neutral',
  proposal: 'neutral',
  procurement: 'neutral',
  poc: 'neutral',
  deployment: 'neutral',
  expansion: 'neutral',
  closed_won: 'positive',
  closed_lost: 'neutral',
};

export const SUPPLY_STAGE_TONES: Record<SupplyStage, StatusTone> = {
  sourced: 'neutral',
  qualifying: 'neutral',
  technical_diligence: 'neutral',
  financial_diligence: 'neutral',
  pricing: 'neutral',
  contracting: 'neutral',
  onboarding: 'neutral',
  live: 'positive',
  renewal: 'warning',
  churned: 'danger',
  rejected: 'neutral',
};

export type DealStageBadgeProps =
  | { side: 'demand'; stage: DemandStage; className?: string }
  | { side: 'supply'; stage: SupplyStage; className?: string };

/**
 * Not named in the direction's component table, but the audit counted deal
 * stage among the domains drawn several ways, and `Account.tsx` holds two
 * private tone functions for it. It belongs with the rest of the vocabulary.
 */
export function DealStageBadge(props: DealStageBadgeProps) {
  const { tone, label } =
    props.side === 'demand'
      ? { tone: DEMAND_STAGE_TONES[props.stage], label: DEMAND_STAGE_LABELS[props.stage] }
      : { tone: SUPPLY_STAGE_TONES[props.stage], label: SUPPLY_STAGE_LABELS[props.stage] };
  return (
    <Badge tone={tone} className={props.className}>
      {props.side === 'supply' && props.stage === 'renewal' ? <ActMark /> : null}
      {label}
    </Badge>
  );
}

// ---------------------------------------------------------------- contracts

/**
 * Paper state.
 *
 * `executed` is neutral, which is the tone change most likely to be questioned.
 * It is a process outcome — the paper is signed, nothing follows from it today
 * — and on the contracts list most rows are executed, so colouring it green
 * paints the whole table and leaves the expiring one indistinguishable.
 * `out_for_signature` is `info` rather than `warning` because it is waiting on
 * the counterparty, not on us.
 */
export const CONTRACT_STATUS_TONES: Record<ContractStatus, StatusTone> = {
  draft: 'neutral',
  in_review: 'neutral',
  in_negotiation: 'neutral',
  out_for_signature: 'info',
  executed: 'neutral',
  expired: 'danger',
  terminated: 'neutral',
};

export const CONTRACT_STATUS_LABELS: Record<ContractStatus, string> = {
  draft: 'Draft',
  in_review: 'In review',
  in_negotiation: 'Negotiating',
  out_for_signature: 'For signature',
  executed: 'Executed',
  expired: 'Expired',
  terminated: 'Terminated',
};

export function ContractStatusBadge({
  status,
  className,
}: {
  status: ContractStatus;
  className?: string;
}) {
  return (
    <Badge tone={CONTRACT_STATUS_TONES[status]} className={className}>
      {status === 'expired' ? <FailMark /> : null}
      {CONTRACT_STATUS_LABELS[status]}
    </Badge>
  );
}

// ------------------------------------------------------------------ renewals

/** Mirrors `RenewalState` in `apps/api/src/services/contracts.ts`, which the
 *  web app cannot import. Expiry minus notice days against today. */
export type RenewalState = 'not_applicable' | 'scheduled' | 'due' | 'expired';

export const RENEWAL_TONES: Record<RenewalState, StatusTone> = {
  not_applicable: 'neutral',
  scheduled: 'neutral',
  due: 'warning',
  expired: 'danger',
};

/**
 * Renewal urgency, which is the one status in the product that is worth money
 * on a deadline: a notice window that quietly opened last week is the most
 * expensive thing in this book to miss.
 *
 * Only the two states that need a person are drawn as badges. A contract with
 * no alarm, or one whose notice is months away, is fine print — this appears in
 * a column, and forty grey chips down a table are forty things to look past
 * before finding the two amber ones. That restraint is `Contracts.tsx`'s
 * original design and it is kept deliberately rather than regularised away.
 */
export function RenewalBadge({
  state,
  noticeAt,
  className,
}: {
  state: RenewalState;
  /** The computed notice date, shown when it is still ahead. */
  noticeAt?: string | Date | null;
  className?: string;
}) {
  if (state === 'due') {
    return (
      <Badge tone="warning" className={className}>
        <ActMark />
        Notice due
      </Badge>
    );
  }
  if (state === 'expired') {
    return (
      <Badge tone="danger" className={className}>
        <FailMark />
        Expired
      </Badge>
    );
  }
  return (
    <span className={cn('text-xs text-muted', className)}>
      {state === 'not_applicable'
        ? 'No alarm'
        : noticeAt
          ? `Notice ${shortDate(noticeAt)}`
          : 'Notice scheduled'}
    </span>
  );
}

// ------------------------------------------------------------ agent activity

/**
 * A run's status is free text on the wire — `agent_runs.status` is a `text`
 * column — so an unrecognised value is shown as it arrived, in neutral, rather
 * than forced into one of the four we know. A status this panel has never heard
 * of is information, not an error.
 *
 * `aborted` reads "Stopped by you" because that is what it means: the reader
 * pressed Stop, or navigated away. "Aborted" describes the process; the person
 * wants to know whether it was them.
 */
export const RUN_STATUS_TONES: Record<string, StatusTone> = {
  running: 'info',
  awaiting_approval: 'warning',
  succeeded: 'neutral',
  aborted: 'neutral',
  failed: 'danger',
};

export const RUN_STATUS_LABELS: Record<string, string> = {
  running: 'Running',
  awaiting_approval: 'Needs you',
  succeeded: 'Succeeded',
  aborted: 'Stopped by you',
  failed: 'Failed',
};

export function RunStatusBadge({ status, className }: { status: string; className?: string }) {
  const tone = RUN_STATUS_TONES[status] ?? 'neutral';
  return (
    <Badge tone={tone} className={className}>
      {status === 'failed' ? <FailMark /> : status === 'awaiting_approval' ? <ActMark /> : null}
      {RUN_STATUS_LABELS[status] ?? humanise(status)}
    </Badge>
  );
}

/** Mirrors `PiggyTaskSummary['state']`: the three live states the queue derives
 *  plus `AGENT_TASK_OUTCOMES`. */
export type TaskState =
  | 'running'
  | 'queued'
  | 'scheduled'
  | 'succeeded'
  | 'failed'
  | 'skipped'
  | 'cancelled';

export const TASK_STATE_TONES: Record<TaskState, StatusTone> = {
  running: 'info',
  queued: 'info',
  scheduled: 'info',
  succeeded: 'neutral',
  failed: 'danger',
  skipped: 'neutral',
  cancelled: 'neutral',
};

export const TASK_STATE_LABELS: Record<TaskState, string> = {
  running: 'Running',
  queued: 'Queued',
  scheduled: 'Scheduled',
  succeeded: 'Succeeded',
  failed: 'Failed',
  skipped: 'Skipped',
  cancelled: 'Cancelled',
};

export function TaskStateBadge({ state, className }: { state: TaskState; className?: string }) {
  return (
    <Badge tone={TASK_STATE_TONES[state]} className={className}>
      {state === 'failed' ? <FailMark /> : null}
      {TASK_STATE_LABELS[state]}
    </Badge>
  );
}

// ------------------------------------------------------------------ approval

/** The five states of a proposed write. Structurally identical to
 *  `PiggyApprovalState` in `piggy/approval-card.tsx`, which owns the
 *  transitions; declared here so this module does not depend on the card. */
export type ApprovalState = 'pending' | 'submitting' | 'applied' | 'rejected' | 'failed';

/**
 * The decided table from the design direction.
 *
 * `applied` is neutral, not green — the confirmation the reader wants is a
 * single positive check beside the record link in the card's status line, not a
 * green block in a transcript where every approved write would then be green.
 * `pending` is the only warning, and it is warning because it is the only state
 * in the product where the agent has stopped and is waiting for a person.
 */
export const APPROVAL_STATE_TONES: Record<ApprovalState, StatusTone> = {
  pending: 'warning',
  submitting: 'neutral',
  applied: 'neutral',
  rejected: 'neutral',
  failed: 'danger',
};

export function ApprovalStateBadge({
  state,
  /** Which way the person answered, so `submitting` can say which. */
  decision,
  className,
}: {
  state: ApprovalState;
  decision?: 'apply' | 'reject' | null;
  className?: string;
}) {
  const label =
    state === 'pending'
      ? 'Needs you'
      : state === 'submitting'
        ? decision === 'reject'
          ? 'Rejecting'
          : 'Applying'
        : state === 'applied'
          ? 'Applied'
          : state === 'rejected'
            ? 'Rejected'
            : 'Not applied';

  return (
    <Badge tone={APPROVAL_STATE_TONES[state]} className={cn('shrink-0', className)}>
      {state === 'pending' ? <ActMark /> : state === 'failed' ? <FailMark /> : null}
      {label}
    </Badge>
  );
}
