import { z } from 'zod';
import { GradialImageSchema, renderImageHTML, type GradialImage } from './image.js';
import { GradialVideoSchema, type GradialVideo } from './video.js';

// ---------------------------------------------------------------------------
// GradialAsset — union of all DAM-backed asset types
//
// Content authors write: { "$type": "dam.assetRef", "assetId": "..." }
// The compiler resolves this to a GradialImage or GradialVideo based on
// the asset's media type in the DAM.
// ---------------------------------------------------------------------------

/** Discriminated union of all compiler-resolved asset types. */
export type GradialAsset = GradialImage | GradialVideo;

// ---------------------------------------------------------------------------
// Zod schema — discriminated on $type
// ---------------------------------------------------------------------------

export const GradialAssetSchema = z.discriminatedUnion('$type', [
  GradialImageSchema,
  GradialVideoSchema,
]);

// ---------------------------------------------------------------------------
// Type guards
// ---------------------------------------------------------------------------

export function isGradialImage(value: unknown): value is GradialImage {
  return (
    typeof value === 'object' &&
    value !== null &&
    (value as Record<string, unknown>).$type === 'gradial.image'
  );
}

export function isGradialVideo(value: unknown): value is GradialVideo {
  return (
    typeof value === 'object' &&
    value !== null &&
    (value as Record<string, unknown>).$type === 'gradial.video'
  );
}

export function isGradialAsset(value: unknown): value is GradialAsset {
  return isGradialImage(value) || isGradialVideo(value);
}

// ---------------------------------------------------------------------------
// HTML string renderers (framework-agnostic)
// ---------------------------------------------------------------------------

/**
 * Renders a GradialVideo to an HTML string.
 * Uses poster.fallback.src for the poster attribute.
 * Includes <source> elements for format variants.
 */
export function renderVideoHTML(
  video: GradialVideo,
  attrs: Record<string, string | number | boolean | undefined | null> = {},
): string {
  const posterSrc = video.poster?.fallback.src;
  const attrPairs: Array<[string, string | number | boolean | undefined | null]> = [
    ...Object.entries(attrs),
    ['src', video.sources?.length ? undefined : video.src],
    ['poster', posterSrc],
    ['width', video.width > 0 ? video.width : undefined],
    ['height', video.height > 0 ? video.height : undefined],
  ];
  const attrString = renderAttrs(attrPairs);

  const sources = (video.sources ?? [])
    .map((s) => `<source${renderAttrs([['src', s.src], ['type', s.type]])}>`)
    .join('');

  // If no format variants, src is on the <video> element itself
  if (!sources) {
    return `<video${attrString}></video>`;
  }
  return `<video${attrString}>${sources}</video>`;
}

/**
 * Renders any GradialAsset to an HTML string.
 * Delegates to renderImageHTML or renderVideoHTML based on $type.
 */
export function renderAssetHTML(
  asset: GradialAsset,
  attrs: Record<string, string | number | boolean | undefined | null> = {},
): string {
  if (asset.$type === 'gradial.image') {
    return renderImageHTML(asset, attrs);
  }
  return renderVideoHTML(asset, attrs);
}

// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------

function renderAttrs(
  pairs: Array<[string, string | number | boolean | undefined | null]>,
): string {
  const rendered = pairs
    .filter(([, value]) => value !== undefined && value !== null && value !== false)
    .map(([name, value]) =>
      value === true
        ? escapeName(name)
        : `${escapeName(name)}="${escapeAttr(String(value))}"`,
    );
  if (!rendered.length) return '';
  return ' ' + rendered.join(' ');
}

function escapeName(name: string): string {
  return name.replace(/[^A-Za-z0-9_:-]/g, '');
}

function escapeAttr(value: string): string {
  return value
    .replace(/&/g, '&amp;')
    .replace(/"/g, '&quot;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;');
}
