/**
 * Normalization utilities for converting between pub.leaflet and site.standard lexicon formats.
 *
 * The standard format (site.standard.*) is used as the canonical representation for
 * reading data from the database, while both formats are accepted for storage.
 *
 * ## Site Field Format
 *
 * The `site` field in site.standard.document supports two URI formats:
 * - AT-URIs (at://did/collection/rkey) - Used when document belongs to an AT Protocol publication
 * - HTTPS URLs (https://example.com) - Used for standalone documents or external sites
 *
 * Both formats are valid and should be handled by consumers.
 */

import type * as PubLeafletDocument from "../api/types/pub/leaflet/document";
import * as PubLeafletPublication from "../api/types/pub/leaflet/publication";
import type * as PubLeafletContent from "../api/types/pub/leaflet/content";
import type * as SiteStandardDocument from "../api/types/site/standard/document";
import type * as SiteStandardPublication from "../api/types/site/standard/publication";
import type * as SiteStandardThemeBasic from "../api/types/site/standard/theme/basic";
import type * as PubLeafletThemeColor from "../api/types/pub/leaflet/theme/color";
import type { $Typed } from "../api/util";
import { AtUri } from "@atproto/syntax";

// Normalized document type - uses the generated site.standard.document type
// with an additional optional theme field for backwards compatibility
export type NormalizedDocument = SiteStandardDocument.Record & {
  // Keep the original theme for components that need leaflet-specific styling
  theme?: PubLeafletPublication.Theme;
  preferences?: SiteStandardPublication.Preferences;
};

// Normalized publication type - uses the generated site.standard.publication type
// with the theme narrowed to only the valid pub.leaflet.publication#theme type
// (isTheme validates that $type is present, so we use $Typed)
// Note: We explicitly list fields rather than using Omit because the generated Record type
// has an index signature [k: string]: unknown that interferes with property typing
export type NormalizedPublication = {
  $type: "site.standard.publication";
  name: string;
  url: string;
  description?: string;
  icon?: SiteStandardPublication.Record["icon"];
  basicTheme?: SiteStandardThemeBasic.Main;
  theme?: $Typed<PubLeafletPublication.Theme>;
  preferences?: SiteStandardPublication.Preferences;
};

/**
 * Checks if the record is a pub.leaflet.document
 */
export function isLeafletDocument(
  record: unknown,
): record is PubLeafletDocument.Record {
  if (!record || typeof record !== "object") return false;
  const r = record as Record<string, unknown>;
  return (
    r.$type === "pub.leaflet.document" ||
    // Legacy records without $type but with pages array
    (Array.isArray(r.pages) && typeof r.author === "string")
  );
}

/**
 * Checks if the record is a site.standard.document
 */
export function isStandardDocument(
  record: unknown,
): record is SiteStandardDocument.Record {
  if (!record || typeof record !== "object") return false;
  const r = record as Record<string, unknown>;
  return r.$type === "site.standard.document";
}

/**
 * Checks if the record is a pub.leaflet.publication
 */
export function isLeafletPublication(
  record: unknown,
): record is PubLeafletPublication.Record {
  if (!record || typeof record !== "object") return false;
  const r = record as Record<string, unknown>;
  return (
    r.$type === "pub.leaflet.publication" ||
    // Legacy records without $type but with name and no url
    (typeof r.name === "string" && !("url" in r))
  );
}

/**
 * Checks if the record is a site.standard.publication
 */
export function isStandardPublication(
  record: unknown,
): record is SiteStandardPublication.Record {
  if (!record || typeof record !== "object") return false;
  const r = record as Record<string, unknown>;
  return r.$type === "site.standard.publication";
}

/**
 * Extracts RGB values from a color union type
 */
function extractRgb(
  color:
    | $Typed<PubLeafletThemeColor.Rgba>
    | $Typed<PubLeafletThemeColor.Rgb>
    | { $type: string }
    | undefined,
): { r: number; g: number; b: number } | undefined {
  if (!color || typeof color !== "object") return undefined;
  const c = color as Record<string, unknown>;
  if (
    typeof c.r === "number" &&
    typeof c.g === "number" &&
    typeof c.b === "number"
  ) {
    return { r: c.r, g: c.g, b: c.b };
  }
  return undefined;
}

/**
 * Converts a pub.leaflet theme to a site.standard.theme.basic format
 */
export function leafletThemeToBasicTheme(
  theme: PubLeafletPublication.Theme | undefined,
): SiteStandardThemeBasic.Main | undefined {
  if (!theme) return undefined;

  const background = extractRgb(theme.backgroundColor);
  const accent =
    extractRgb(theme.accentBackground) || extractRgb(theme.primary);
  const accentForeground = extractRgb(theme.accentText);

  // If we don't have the required colors, return undefined
  if (!background || !accent) return undefined;

  // Default foreground to dark if not specified
  const foreground = { r: 0, g: 0, b: 0 };

  // Default accent foreground to white if not specified
  const finalAccentForeground = accentForeground || { r: 255, g: 255, b: 255 };

  return {
    $type: "site.standard.theme.basic",
    background: { $type: "site.standard.theme.color#rgb", ...background },
    foreground: { $type: "site.standard.theme.color#rgb", ...foreground },
    accent: { $type: "site.standard.theme.color#rgb", ...accent },
    accentForeground: {
      $type: "site.standard.theme.color#rgb",
      ...finalAccentForeground,
    },
  };
}

/**
 * Normalizes a document record from either format to the standard format.
 *
 * @param record - The document record from the database (either pub.leaflet or site.standard)
 * @param uri - Optional document URI, used to extract the rkey for the path field when normalizing pub.leaflet records
 * @returns A normalized document in site.standard format, or null if invalid/unrecognized
 */
export function normalizeDocument(
  record: unknown,
  uri?: string,
): NormalizedDocument | null {
  if (!record || typeof record !== "object") return null;

  // Pass through site.standard records directly (theme is already in correct format if present)
  if (isStandardDocument(record)) {
    const preferences = record.preferences as
      | SiteStandardPublication.Preferences
      | undefined;
    return {
      ...record,
      theme: record.theme,
      preferences,
    } as NormalizedDocument;
  }

  if (isLeafletDocument(record)) {
    // Convert from pub.leaflet to site.standard
    const publishedAt = record.publishedAt;

    if (!publishedAt) {
      return null;
    }

    // For standalone documents (no publication), construct a site URL from the author
    // This matches the pattern used in publishToPublication.ts for new standalone docs
    const site = record.publication || `https://leaflet.pub/p/${record.author}`;

    // Extract path from URI if available
    const path = uri ? new AtUri(uri).rkey : undefined;

    // Wrap pages in pub.leaflet.content structure
    const content: $Typed<PubLeafletContent.Main> | undefined = record.pages
      ? {
          $type: "pub.leaflet.content" as const,
          pages: record.pages,
        }
      : undefined;

    // Extract preferences if present (available after lexicon rebuild)
    const leafletPrefs = (record as Record<string, unknown>)
      .preferences as SiteStandardPublication.Preferences | undefined;

    return {
      $type: "site.standard.document",
      title: record.title,
      site,
      path,
      publishedAt,
      description: record.description,
      tags: record.tags,
      coverImage: record.coverImage,
      bskyPostRef: record.postRef,
      content,
      theme: record.theme,
      preferences: leafletPrefs
        ? { ...leafletPrefs, $type: "site.standard.publication#preferences" as const }
        : undefined,
    };
  }

  return null;
}

/**
 * Normalizes a publication record from either format to the standard format.
 *
 * @param record - The publication record from the database (either pub.leaflet or site.standard)
 * @returns A normalized publication in site.standard format, or null if invalid/unrecognized
 */
export function normalizePublication(
  record: unknown,
): NormalizedPublication | null {
  if (!record || typeof record !== "object") return null;

  // Pass through site.standard records directly, but validate the theme
  if (isStandardPublication(record)) {
    // Validate theme - only keep if it's a valid pub.leaflet.publication#theme
    const theme = PubLeafletPublication.isTheme(record.theme)
      ? (record.theme as $Typed<PubLeafletPublication.Theme>)
      : undefined;
    return {
      ...record,
      theme,
    };
  }

  if (isLeafletPublication(record)) {
    // Convert from pub.leaflet to site.standard
    const url = record.base_path ? `https://${record.base_path}` : undefined;

    if (!url) {
      return null;
    }

    const basicTheme = leafletThemeToBasicTheme(record.theme);

    // Validate theme - only keep if it's a valid pub.leaflet.publication#theme with $type set
    // For legacy records without $type, add it during normalization
    let theme: $Typed<PubLeafletPublication.Theme> | undefined;
    if (record.theme) {
      if (PubLeafletPublication.isTheme(record.theme)) {
        theme = record.theme as $Typed<PubLeafletPublication.Theme>;
      } else {
        // Legacy theme without $type - add it
        theme = {
          ...record.theme,
          $type: "pub.leaflet.publication#theme",
        };
      }
    }

    // Convert preferences to site.standard format (strip/replace $type)
    const preferences: SiteStandardPublication.Preferences | undefined =
      record.preferences
        ? {
            showInDiscover: record.preferences.showInDiscover,
            showComments: record.preferences.showComments,
            showMentions: record.preferences.showMentions,
            showPrevNext: record.preferences.showPrevNext,
            showRecommends: record.preferences.showRecommends,
          }
        : undefined;

    return {
      $type: "site.standard.publication",
      name: record.name,
      url,
      description: record.description,
      icon: record.icon,
      basicTheme,
      theme,
      preferences,
    };
  }

  return null;
}

/**
 * Type guard to check if a normalized document has leaflet content
 */
export function hasLeafletContent(
  doc: NormalizedDocument,
): doc is NormalizedDocument & {
  content: $Typed<PubLeafletContent.Main>;
} {
  return (
    doc.content !== undefined &&
    (doc.content as { $type?: string }).$type === "pub.leaflet.content"
  );
}

/**
 * Gets the pages array from a normalized document, handling both formats
 */
export function getDocumentPages(
  doc: NormalizedDocument,
): PubLeafletContent.Main["pages"] | undefined {
  if (!doc.content) return undefined;

  if (hasLeafletContent(doc)) {
    return doc.content.pages;
  }

  // Unknown content type
  return undefined;
}
