import {
  ChangeDetectionStrategy,
  Component,
  ElementRef,
  ViewEncapsulation,
  computed,
  effect,
  inject,
  input,
} from '@angular/core';
import * as Plot from '@observablehq/plot';
import {
  chartId,
  findChartSvg,
  injectSvgTitleDesc,
  prefersReducedMotion,
  resolveTheme,
} from '../_internal/a11y';
import type { ChartBaseInputs } from '../_internal/chart-types';
import { paletteFor } from '../_internal/palette';

/**
 * `<lib-stacked-bar-chart>` — bars stacked by a series key,
 * categorised on an X axis. Use case in the audit-log: events per
 * day, stacked by event_type, so a viewer sees both the total
 * volume per day and the breakdown.
 *
 * Backed by `Plot.barY` with the `fill` channel set to the series
 * key — Plot then auto-stacks the values per X bucket. Renders the
 * same a11y envelope as the other charts.
 *
 * The component accepts a flat array of rows, each carrying the
 * X-axis bucket, the series key, and the value. Pre-pivoted data
 * stays the simplest input model for consumers to feed.
 */

export interface StackedBarRow {
  readonly [key: string]: unknown;
}

export interface StackedBarChartInputs<T> extends ChartBaseInputs<T> {
  readonly xKey: keyof T & string;
  readonly yKey: keyof T & string;
  readonly seriesKey: keyof T & string;
  readonly xLabel?: string;
  readonly yLabel?: string;
}

@Component({
  selector: 'lib-stacked-bar-chart',
  templateUrl: './stacked-bar-chart.html',
  styleUrl: './stacked-bar-chart.scss',
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
})
export class StackedBarChart<T extends StackedBarRow> {
  private readonly host = inject(ElementRef<HTMLElement>);

  readonly data = input.required<readonly T[]>();
  readonly xKey = input.required<keyof T & string>();
  readonly yKey = input.required<keyof T & string>();
  readonly seriesKey = input.required<keyof T & string>();
  readonly caption = input.required<string>();
  readonly description = input.required<string>();
  readonly ariaLabel = input.required<string>();
  readonly xLabel = input<string | undefined>(undefined);
  readonly yLabel = input<string | undefined>(undefined);
  readonly colorScheme = input<'sequential' | 'categorical'>('categorical');

  protected readonly chartId = chartId();
  protected readonly titleId = `${this.chartId}-title`;
  protected readonly descId = `${this.chartId}-desc`;

  /** Tabular fallback row shape — flat, one row per datum. */
  protected readonly tableRows = computed(() =>
    this.data().map((row) => ({
      x: String(row[this.xKey()]),
      series: String(row[this.seriesKey()]),
      y: Number(row[this.yKey()]),
    })),
  );

  /** Unique series labels in encounter order — drives the legend. */
  protected readonly seriesLabels = computed(() => {
    const seen = new Set<string>();
    for (const row of this.data()) {
      seen.add(String(row[this.seriesKey()]));
    }
    return Array.from(seen);
  });

  constructor() {
    effect(() => {
      this.renderPlot();
    });
  }

  private renderPlot(): void {
    const canvas = (this.host.nativeElement as HTMLElement).querySelector<HTMLDivElement>(
      '.chart-canvas',
    );
    if (!canvas) {
      return;
    }
    const data = this.data();
    const xKey = this.xKey();
    const yKey = this.yKey();
    const seriesKey = this.seriesKey();
    const xLabel = this.xLabel() ?? null;
    const yLabel = this.yLabel() ?? null;
    const palette = paletteFor(this.colorScheme(), resolveTheme(), this.seriesLabels().length);

    const plot = Plot.plot({
      width: canvas.clientWidth || 720,
      marginLeft: 56,
      marginBottom: 40,
      x: { label: xLabel, type: 'band' },
      y: { label: yLabel, grid: true, nice: true },
      color: {
        type: 'ordinal',
        domain: this.seriesLabels() as string[],
        range: palette,
        legend: true,
      },
      marks: [
        Plot.barY(data as Plot.Data, {
          x: xKey,
          y: yKey,
          fill: seriesKey,
        }),
        Plot.ruleY([0]),
      ],
    });

    const svg = findChartSvg(plot);
    if (svg) {
      injectSvgTitleDesc(svg, this.caption(), this.description());
    }
    if (prefersReducedMotion()) {
      plot.setAttribute('data-no-transitions', '');
    }

    canvas.replaceChildren(plot);
  }
}
