import { decompose } from '@/lib/demo-kit/reward';
import type { RewardComponent, RewardSpec, RewardValues } from '@/lib/demo-kit/types';
import { Term } from '@/components/site/Term';
import { cn } from '@/lib/utils';
import { Chip, EditedChip } from './chrome';
import { DASH, formatNumber, formatOrDash } from './format';

type Role = RewardComponent['role'];

/**
 * The three roles a reward term can play. All three are glossary terms and
 * render through `Term`, so the chip carries its gloss on hover, focus and
 * tap. The counterweight is introduced in the sentence under the table as
 * "the term that takes points away" before its label, because that phrase is
 * the lesson and the label is only its name.
 */
const ROLE_META: Record<Role, { label: string; variant: 'outline' | 'warning' | 'muted' }> = {
  objective: { label: 'Objective', variant: 'outline' },
  counterweight: { label: 'Counterweight', variant: 'warning' },
  gate: { label: 'Gate', variant: 'muted' },
};

function RoleChip({ role }: { role: Role }) {
  const meta = ROLE_META[role];
  return (
    <Term term={role} className="no-underline">
      <Chip variant={meta.variant} className="cursor-help">
        {meta.label}
      </Chip>
    </Term>
  );
}

export interface RewardBreakdownProps {
  spec: RewardSpec;
  values: RewardValues;
  /** Unweighted diagnostics. Rendered, never summed — the contract is explicit. */
  metrics?: Record<string, number | null>;
  /**
   * Components carrying the weights in force. Pass the output of `reweight()`
   * when the visitor has edited them; omit for the shipped reward.
   */
  components?: readonly RewardComponent[];
  /** Set when `components` came from the visitor rather than the environment. */
  edited?: boolean;
  className?: string;
}

/**
 * `score × weight = value`, per component, plus the total.
 *
 * The counterweight row is called out because it is the row that makes the
 * reward an opinion rather than a scoreboard. Everyone understands "pay for
 * solving it". The interesting engineering is the term that takes points away,
 * and an exec who leaves this page understanding only that has got the point.
 *
 * A term the environment did not score prints `—` with a title, never 0.000:
 * a zero is a measurement and a dash is the absence of one. Below `sm` the
 * SCORE column folds under the label as `score 0.125` so four columns never
 * force the table to scroll.
 */
export function RewardBreakdown({
  spec,
  values,
  metrics,
  components,
  edited = false,
  className,
}: RewardBreakdownProps) {
  const inForce = components ?? spec.components;
  const { rows, total } = decompose(values, inForce);
  const counterweights = rows.filter((row) => row.role === 'counterweight');

  return (
    <div className={cn('overflow-hidden rounded-xl border border-border bg-surface', className)}>
        <div className="overflow-x-auto">
          <table className="w-full border-collapse">
            <caption className="sr-only">
              Reward components, their weights and their contribution to the total score
            </caption>
            <thead>
              <tr className="bg-surface-3 text-left font-mono text-label uppercase tracking-label text-muted">
                <th scope="col" className="px-3 py-2 font-medium">
                  Component
                </th>
                <th scope="col" className="hidden px-2 py-2 text-right font-medium sm:table-cell">
                  Score
                </th>
                <th scope="col" className="px-2 py-2 text-right font-medium">
                  Weight
                </th>
                <th scope="col" className="px-3 py-2 text-right font-medium">
                  Value
                </th>
              </tr>
            </thead>
            <tbody className="divide-y divide-border">
              {rows.map((row) => {
                const isCounterweight = row.role === 'counterweight';
                return (
                  <tr key={row.key} className={cn(isCounterweight && 'bg-accent-subtle')}>
                    <th scope="row" className="max-w-[24rem] px-3 py-2.5 text-left font-normal align-top">
                      <span className="flex flex-wrap items-center gap-1.5">
                        <span className="text-ui font-medium text-fg">{row.label}</span>
                        <RoleChip role={row.role} />
                      </span>
                      <span className="mt-0.5 block text-small text-fg-2">{row.description}</span>
                      <span className="mt-1 flex flex-wrap items-baseline gap-x-3 font-mono text-label text-muted">
                        <span>{row.key}</span>
                        <span className="sm:hidden">
                          <span className="uppercase tracking-label">score</span>{' '}
                          <span className="text-mono-data text-fg">
                            {row.score === null ? (
                              <span className="text-muted" title="not scored">
                                {DASH}
                              </span>
                            ) : (
                              formatNumber(row.score)
                            )}
                          </span>
                        </span>
                      </span>
                    </th>
                    <td className="hidden px-2 py-2.5 text-right align-top font-mono text-mono-data sm:table-cell">
                      {row.score === null ? (
                        <span className="text-muted" title="not scored">
                          {DASH}
                        </span>
                      ) : (
                        formatNumber(row.score)
                      )}
                    </td>
                    <td className="px-2 py-2.5 text-right align-top font-mono text-mono-data">
                      <span className={cn(edited && 'font-semibold text-accent-fg')}>
                        {formatNumber(row.weight, 2)}
                      </span>
                    </td>
                    <td className="px-3 py-2.5 text-right align-top font-mono text-stat text-fg">
                      {row.value === null ? (
                        <span className="font-normal text-muted" title="not scored">
                          {DASH}
                        </span>
                      ) : (
                        formatNumber(row.value)
                      )}
                    </td>
                  </tr>
                );
              })}
            </tbody>
            <tfoot>
              <tr className="border-t-2 border-border-strong bg-surface-2">
                <th scope="row" className="px-3 py-2.5 text-left" colSpan={2}>
                  <span className="flex items-center gap-2 text-ui font-medium text-fg">
                    Total reward
                    {edited ? <EditedChip /> : null}
                  </span>
                </th>
                <td className="hidden sm:table-cell" />
                <td className="px-3 py-2.5 text-right font-mono text-[15px] font-semibold text-accent-fg">
                  {total === null ? (
                    <span className="font-normal text-muted" title="not scored">
                      {DASH}
                    </span>
                  ) : (
                    formatOrDash(total)
                  )}
                </td>
              </tr>
            </tfoot>
          </table>
        </div>

        {counterweights.length > 0 ? (
          <p className="max-w-measure border-t border-border px-3 py-3 text-prose text-fg-2">
            <span className="font-medium text-fg">
              {counterweights.map((row) => row.label).join(' and ')}
            </span>{' '}
            {counterweights.length > 1 ? 'are the terms that take points away' : 'is the term that takes points away'}
            : without one pulling the other way, the cheapest way to maximise the objective is a
            behaviour you would never ship, and the model will find it.
          </p>
        ) : null}

        {spec.metrics && spec.metrics.length > 0 ? (
          <div className="border-t border-border px-3 py-2.5">
            <h4 className="font-mono text-label uppercase tracking-label text-muted">
              Diagnostics — reported, never summed
            </h4>
            <dl className="mt-1.5 grid grid-cols-1 gap-x-6 gap-y-1 sm:grid-cols-2">
              {spec.metrics.map((metric) => (
                <div key={metric.key} className="flex items-baseline justify-between gap-2">
                  <dt className="text-small text-muted" title={metric.description}>
                    {metric.label}{' '}
                    <span className="font-mono text-label">{metric.key}</span>
                  </dt>
                  <dd className="font-mono text-mono-data text-fg">
                    {formatOrDash(metrics?.[metric.key] ?? null)}
                  </dd>
                </div>
              ))}
            </dl>
          </div>
        ) : null}
    </div>
  );
}
