import type { FeatureCollection, Geometry } from 'geojson';

/**
 * Overlay catalogue client (M1C).
 *
 * Overlays are the historical and reference maps users superimpose on the
 * satellite basemap to guide their own division of the land (SRS 3.2). The
 * catalogue and the data both come from the Laravel API rather than being
 * bundled here, so an overlay can be added or corrected without rebuilding
 * and redeploying the frontend -- which is also what the admin dashboard in a
 * later milestone (SRS 3.2.10) will drive.
 */

/**
 * Base URL of the Laravel API.
 *
 * Environment-driven with a localhost fallback. The earlier Angular build had
 * its API host hard-coded in two places, which the Phase 1 review flagged as
 * one of the things to fix in the rebuild; keeping it in the environment is
 * that fix.
 */
export const API_BASE_URL = (
  process.env.NEXT_PUBLIC_API_BASE_URL ?? 'http://127.0.0.1:8000'
).replace(/\/+$/, '');

/** How an overlay's payload is stored, and therefore how it is drawn. */
export type OverlayKind = 'vector' | 'raster';

/** Digital (already geographic data) or analog (a scanned paper map). */
export type OverlaySourceType = 'digital' | 'analog';

/** [lng, lat] of a scan's corners: top-left, top-right, bottom-right, bottom-left. */
export type ImageCorners = [
  [number, number],
  [number, number],
  [number, number],
  [number, number],
];

export interface OverlayMeta {
  id: string;
  slug: string;
  title: string;
  description: string | null;
  kind: OverlayKind;
  sourceType: OverlaySourceType;
  sourceName: string | null;
  sourceUrl: string | null;
  licence: string | null;
  /** Raster overlays only: [[west, south], [east, north]]. */
  bounds: [[number, number], [number, number]] | null;
  /**
   * Raster overlays only: four independent corners in Mapbox image-source
   * order (top-left, top-right, bottom-right, bottom-left). Preferred over
   * `bounds` because a real scan is rarely a north-up rectangle -- this lets
   * it be rotated and skewed into register.
   */
  corners: ImageCorners | null;
  opacity: number;
  fillColor: string | null;
  lineColor: string | null;
  /** Feature property carrying the category label, e.g. "Area". */
  categoryProperty: string | null;
  displayOrder: number;
  year: number | null;
  /**
   * False when the overlay is agreed and catalogued but its file has not been
   * supplied yet -- the state the analog overlay sits in until its scan is
   * georeferenced. The UI shows these as pending rather than offering a toggle
   * that would load nothing.
   */
  hasData: boolean;
}

/** The overlay catalogue, already ordered for display by the API. */
export async function fetchOverlayCatalogue(
  signal?: AbortSignal,
): Promise<OverlayMeta[]> {
  const res = await fetch(`${API_BASE_URL}/api/overlays`, { signal });
  if (!res.ok) {
    throw new Error(`Overlay catalogue unavailable (${res.status})`);
  }
  const json = (await res.json()) as { overlays?: OverlayMeta[] };
  return json.overlays ?? [];
}

/** URL of an overlay's payload -- GeoJSON, or the image for a raster overlay. */
export function overlayDataUrl(slug: string): string {
  return `${API_BASE_URL}/api/overlays/${encodeURIComponent(slug)}/data`;
}

/**
 * Where a raster overlay's corners sit on the map, or null if it has not been
 * georeferenced yet. Four explicit corners win; an axis-aligned `bounds` box is
 * the fallback.
 */
export function imageCorners(overlay: OverlayMeta): ImageCorners | null {
  if (overlay.corners && overlay.corners.length === 4) return overlay.corners;
  if (overlay.bounds) {
    const [[west, south], [east, north]] = overlay.bounds;
    return [
      [west, north],
      [east, north],
      [east, south],
      [west, south],
    ];
  }
  return null;
}

/**
 * Serialise corners for `php artisan overlay:attach --corners=...`, rounded to
 * six decimal places (~10 cm) -- far finer than a scan can be registered to.
 */
export function formatCorners(corners: ImageCorners): string {
  return corners.map(([lng, lat]) => `${lng.toFixed(6)},${lat.toFixed(6)}`).join(';');
}

/** Fetch a vector overlay's GeoJSON. */
export async function fetchOverlayGeoJSON(
  slug: string,
  signal?: AbortSignal,
): Promise<FeatureCollection<Geometry>> {
  const res = await fetch(overlayDataUrl(slug), { signal });
  if (!res.ok) {
    throw new Error(`Overlay data unavailable (${res.status})`);
  }
  return (await res.json()) as FeatureCollection<Geometry>;
}

/**
 * Distinct category values present in an overlay, in first-seen order.
 *
 * Used to colour a multi-category overlay -- Areas A, B and C being the case
 * this exists for -- without hard-coding what those categories are, since the
 * analog overlays will have entirely different ones.
 */
export function overlayCategories(
  data: FeatureCollection<Geometry>,
  property: string,
): string[] {
  const seen: string[] = [];
  for (const feature of data.features) {
    const value = feature.properties?.[property];
    if (typeof value !== 'string') continue;
    if (!seen.includes(value)) seen.push(value);
  }
  return seen;
}

/**
 * Fallback palette for overlays whose data does not carry its own colours.
 *
 * Deliberately free of blue, green and orange: those are Party A, B and C.
 * An earlier version of this palette included near-copies of all three, which
 * mattered more than it looks -- the Areas A/B/C overlay also uses the letters
 * A, B and C, so a matching colour would invite a user to read Oslo Area A as
 * their own Party A.
 */
const CATEGORY_PALETTE = [
  '#a855f7', // purple
  '#ec4899', // pink
  '#eab308', // yellow
  '#64748b', // slate
  '#a16207', // ochre
  '#e879f9', // fuchsia
  '#94a3b8', // light slate
  '#be123c', // rose
];

/**
 * Feature property carrying an explicit colour. Follows the simplestyle-spec
 * `fill` convention, so an overlay's colours can travel with its data rather
 * than being hard-coded here -- this app has no business knowing what colour
 * Area B should be.
 */
const FILL_PROPERTY = 'fill';

/**
 * Mapbox paint expression: a feature's own `fill` colour if it has one,
 * otherwise a palette colour chosen by its category, otherwise the overlay's
 * default colour.
 */
export function categoryColorExpression(
  categories: string[],
  property: string,
  fallback: string,
): string | unknown[] {
  let byCategory: string | unknown[] = fallback;
  if (categories.length > 0) {
    const match: unknown[] = ['match', ['get', property]];
    categories.forEach((category, i) => {
      match.push(category, CATEGORY_PALETTE[i % CATEGORY_PALETTE.length]);
    });
    match.push(fallback);
    byCategory = match;
  }
  return ['coalesce', ['get', FILL_PROPERTY], byCategory];
}

export interface LegendEntry {
  label: string;
  color: string;
}

/**
 * Legend for a multi-category overlay: one entry per category, in the order
 * the data lists them, coloured exactly as the map draws them. Without this an
 * eight-category overlay like Areas A/B/C is unreadable.
 */
export function overlayLegend(
  data: FeatureCollection<Geometry>,
  property: string,
  fallback: string,
): LegendEntry[] {
  const categories = overlayCategories(data, property);
  return categories.map((label, i) => {
    const withFill = data.features.find(
      (f) => f.properties?.[property] === label && typeof f.properties?.[FILL_PROPERTY] === 'string',
    );
    const color =
      (withFill?.properties?.[FILL_PROPERTY] as string | undefined) ??
      CATEGORY_PALETTE[i % CATEGORY_PALETTE.length] ??
      fallback;
    return { label, color };
  });
}
