/**
 * @fileoverview Landing page configuration lint rules.
 * Enforces content limits (tagline length, logo size, link count, repoRoot
 * shape) at startup so surprising input fails loudly instead of quietly at
 * render time.
 * @module src/linter/rules/landing-rules
 */

import {
  CONNECT_SNIPPET_TABS,
  GITHUB_REPO_ROOT_PATTERN,
  isSafeCssColor,
  LANDING_MAX_ENV_EXAMPLE,
  LANDING_MAX_LINKS,
  LANDING_MAX_LOGO_BYTES,
  LANDING_MAX_TAGLINE_LENGTH,
} from '@/core/serverManifest.js';

import type { LintDiagnostic } from '../types.js';

const LANDING = 'landing';
const NAME = 'landing';

function error(rule: string, message: string): LintDiagnostic {
  return {
    rule,
    severity: 'error',
    message,
    definitionType: LANDING,
    definitionName: NAME,
  };
}

function warn(rule: string, message: string): LintDiagnostic {
  return {
    rule,
    severity: 'warning',
    message,
    definitionType: LANDING,
    definitionName: NAME,
  };
}

/** Approximate byte size of a data URI's decoded content, given its raw length. */
function approximateDataUriBytes(uri: string): number {
  // data:image/png;base64,AAAA...  — base64-encoded payload is ~4/3× the raw bytes.
  const commaIndex = uri.indexOf(',');
  if (commaIndex < 0) return uri.length;
  const payload = uri.slice(commaIndex + 1);
  // For base64, decoded size = (payload length * 3) / 4, less padding.
  if (uri.startsWith('data:') && /;base64/i.test(uri.slice(0, commaIndex))) {
    const padding = payload.endsWith('==') ? 2 : payload.endsWith('=') ? 1 : 0;
    return Math.floor((payload.length * 3) / 4) - padding;
  }
  // URL-encoded payload — treat byte count as character count.
  return payload.length;
}

/**
 * Validate the consumer-supplied `landing` object shape.
 * Returns an empty array when `landing` is undefined (nothing to validate)
 * or when the object passes every rule.
 */
export function lintLandingConfig(landing: unknown): LintDiagnostic[] {
  if (landing == null) return [];
  if (typeof landing !== 'object' || Array.isArray(landing)) {
    return [error('landing-shape', 'landing must be a plain object.')];
  }

  const diagnostics: LintDiagnostic[] = [];
  const l = landing as Record<string, unknown>;

  // tagline
  if (l.tagline != null) {
    if (typeof l.tagline !== 'string') {
      diagnostics.push(error('landing-tagline-type', 'landing.tagline must be a string.'));
    } else if (l.tagline.length > LANDING_MAX_TAGLINE_LENGTH) {
      diagnostics.push(
        error(
          'landing-tagline-length',
          `landing.tagline is ${l.tagline.length} chars; max is ${LANDING_MAX_TAGLINE_LENGTH}.`,
        ),
      );
    }
  }

  // logo
  if (l.logo != null) {
    if (typeof l.logo !== 'string') {
      diagnostics.push(error('landing-logo-type', 'landing.logo must be a string.'));
    } else if (l.logo.startsWith('data:')) {
      const bytes = approximateDataUriBytes(l.logo);
      if (bytes > LANDING_MAX_LOGO_BYTES) {
        diagnostics.push(
          error(
            'landing-logo-size',
            `landing.logo data URI is ~${bytes} bytes; max is ${LANDING_MAX_LOGO_BYTES}. Inline a smaller asset or host it separately.`,
          ),
        );
      }
    }
  }

  // links
  if (l.links != null) {
    if (!Array.isArray(l.links)) {
      diagnostics.push(error('landing-links-type', 'landing.links must be an array.'));
    } else {
      if (l.links.length > LANDING_MAX_LINKS) {
        diagnostics.push(
          warn(
            'landing-links-count',
            `landing.links has ${l.links.length} entries; max is ${LANDING_MAX_LINKS}. Extras will be dropped.`,
          ),
        );
      }
      for (const [i, rawLink] of l.links.entries()) {
        if (!rawLink || typeof rawLink !== 'object') {
          diagnostics.push(
            error(
              'landing-link-shape',
              `landing.links[${i}] must be an object with href and label.`,
            ),
          );
          continue;
        }
        const link = rawLink as Record<string, unknown>;
        if (typeof link.href !== 'string' || link.href.length === 0) {
          diagnostics.push(
            error('landing-link-href', `landing.links[${i}].href must be a non-empty string.`),
          );
        }
        if (typeof link.label !== 'string' || link.label.length === 0) {
          diagnostics.push(
            error('landing-link-label', `landing.links[${i}].label must be a non-empty string.`),
          );
        }
      }
    }
  }

  // repoRoot
  if (l.repoRoot != null) {
    if (typeof l.repoRoot !== 'string') {
      diagnostics.push(error('landing-repo-root-type', 'landing.repoRoot must be a string.'));
    } else if (!GITHUB_REPO_ROOT_PATTERN.test(l.repoRoot)) {
      diagnostics.push(
        error(
          'landing-repo-root-shape',
          `landing.repoRoot must match https://github.com/{owner}/{repo} — got "${l.repoRoot}".`,
        ),
      );
    }
  }

  // envExample
  if (l.envExample != null) {
    if (typeof l.envExample !== 'object' || Array.isArray(l.envExample)) {
      diagnostics.push(
        error('landing-env-example-type', 'landing.envExample must be a plain object.'),
      );
    } else {
      const entries = Object.entries(l.envExample as Record<string, unknown>);
      if (entries.length > LANDING_MAX_ENV_EXAMPLE) {
        diagnostics.push(
          warn(
            'landing-env-example-count',
            `landing.envExample has ${entries.length} entries; max is ${LANDING_MAX_ENV_EXAMPLE}. Extras will be dropped.`,
          ),
        );
      }
      for (const [key, value] of entries) {
        if (!/^[A-Z][A-Z0-9_]*$/.test(key)) {
          diagnostics.push(
            warn(
              'landing-env-example-key',
              `landing.envExample["${key}"] should be SCREAMING_SNAKE_CASE (env var convention).`,
            ),
          );
        }
        if (typeof value !== 'string') {
          diagnostics.push(
            error(
              'landing-env-example-value',
              `landing.envExample["${key}"] must be a string — got ${typeof value}.`,
            ),
          );
        }
      }
    }
  }

  // connectSnippets
  if (l.connectSnippets != null) {
    if (typeof l.connectSnippets !== 'object' || Array.isArray(l.connectSnippets)) {
      diagnostics.push(
        error(
          'landing-connect-snippets-type',
          `landing.connectSnippets must be a plain object keyed by tab id (${CONNECT_SNIPPET_TABS.join(', ')}).`,
        ),
      );
    } else {
      const allowedTabs = new Set<string>(CONNECT_SNIPPET_TABS);
      for (const [key, value] of Object.entries(l.connectSnippets as Record<string, unknown>)) {
        if (!allowedTabs.has(key)) {
          diagnostics.push(
            warn(
              'landing-connect-snippets-key',
              `landing.connectSnippets["${key}"] is not a recognized tab id. Allowed: ${CONNECT_SNIPPET_TABS.join(', ')}. Extra keys will be dropped.`,
            ),
          );
          continue;
        }
        if (typeof value !== 'string') {
          diagnostics.push(
            error(
              'landing-connect-snippets-value',
              `landing.connectSnippets["${key}"] must be a string — got ${typeof value}.`,
            ),
          );
        } else if (value.length === 0) {
          diagnostics.push(
            warn(
              'landing-connect-snippets-empty',
              `landing.connectSnippets["${key}"] is an empty string; falling back to the derived snippet.`,
            ),
          );
        }
      }
    }
  }

  // theme.accent
  if (l.theme != null) {
    if (typeof l.theme !== 'object' || Array.isArray(l.theme)) {
      diagnostics.push(error('landing-theme-type', 'landing.theme must be a plain object.'));
    } else {
      const theme = l.theme as Record<string, unknown>;
      if (theme.accent != null) {
        if (typeof theme.accent !== 'string') {
          diagnostics.push(error('landing-theme-accent', 'landing.theme.accent must be a string.'));
        } else if (!isSafeCssColor(theme.accent)) {
          diagnostics.push(
            error(
              'landing-theme-accent-format',
              `landing.theme.accent ${JSON.stringify(theme.accent)} is not a recognized CSS color. Use a hex literal (#6366f1), named color (indigo), or functional form (rgb(...), oklch(...), color-mix(...)). Values containing ; { } < > \\ or CSS comments are rejected.`,
            ),
          );
        }
      }
    }
  }

  return diagnostics;
}
