export const STRUCTURED_FIELD_KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/;
export const MAX_STRUCTURED_FIELDS = 100;
export const RESERVED_STRUCTURED_FIELD_KEYS = new Set(['title', 'excerpt', 'content']);
const STRUCTURED_PAYLOAD_PREFIX = 'TPZ_STRUCTURED_V1:';

export type StructuredFields = Record<string, string>;

export function serializeStructuredFields(fields: StructuredFields): string {
  const canonical = JSON.stringify(canonicalizeStructuredFields(fields));
  return `${STRUCTURED_PAYLOAD_PREFIX}${Buffer.from(canonical, 'utf8').toString('base64')}`;
}

export function parseSerializedStructuredFields(raw: string | null): StructuredFields | undefined {
  if (!raw || !raw.startsWith(STRUCTURED_PAYLOAD_PREFIX)) {
    return undefined;
  }

  try {
    const encoded = raw.slice(STRUCTURED_PAYLOAD_PREFIX.length);
    const parsed: unknown = JSON.parse(Buffer.from(encoded, 'base64').toString('utf8'));
    if (!isStructuredFields(parsed)) {
      throw new Error('Structured payload must contain string values.');
    }
    return canonicalizeStructuredFields(parsed);
  } catch (error) {
    throw new Error(
      `Invalid structured payload: ${error instanceof Error ? error.message : 'decode failed'}`
    );
  }
}

export function stripHtmlTags(value: string): string {
  let text = '';
  let inTag = false;
  let quote: '"' | "'" | undefined;

  for (const character of value) {
    if (!inTag) {
      if (character === '<') {
        inTag = true;
      } else {
        text += character;
      }
      continue;
    }

    if (quote !== undefined) {
      if (character === quote) {
        quote = undefined;
      }
      continue;
    }

    if (character === '"' || character === "'") {
      quote = character;
    } else if (character === '>') {
      inTag = false;
    }
  }

  return text;
}

export function countSanitizedCharacters(value: string): number {
  return Array.from(stripHtmlTags(value)).length;
}

export function canonicalizeStructuredFields(fields: StructuredFields): StructuredFields {
  return Object.fromEntries(
    Object.entries(fields).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
  );
}

export function validateStructuredFields(fields: unknown, maxChars: number): asserts fields is StructuredFields {
  if (!fields || typeof fields !== 'object' || Array.isArray(fields)) {
    throw new Error('Fields must be an object containing string values.');
  }

  const entries = Object.entries(fields);
  if (entries.length > MAX_STRUCTURED_FIELDS) {
    throw new Error(`Fields must contain at most ${MAX_STRUCTURED_FIELDS} entries.`);
  }

  let totalCharacters = 0;
  for (const [key, value] of entries) {
    if (!STRUCTURED_FIELD_KEY_PATTERN.test(key)) {
      throw new Error(`Invalid structured field key: ${key}`);
    }
    if (RESERVED_STRUCTURED_FIELD_KEYS.has(key)) {
      throw new Error(`Structured field key is reserved: ${key}`);
    }
    if (typeof value !== 'string') {
      throw new Error(`Structured field value must be a string: ${key}`);
    }
    if (countSanitizedCharacters(value) > maxChars) {
      throw new Error(`Structured field value exceeds ${maxChars} characters: ${key}`);
    }
    totalCharacters += countSanitizedCharacters(value);
  }

  if (totalCharacters > maxChars) {
    throw new Error(`Structured fields exceed the ${maxChars} character limit.`);
  }
}

export function mergeStructuredFields(
  coreFields: Partial<StructuredFields>,
  customFields: StructuredFields | undefined,
  maxChars: number
): StructuredFields {
  const merged: StructuredFields = {};
  for (const key of ['title', 'excerpt', 'content']) {
    const value = coreFields[key];
    if (value !== undefined) {
      merged[key] = value;
    }
  }

  if (customFields !== undefined) {
    validateStructuredFields(customFields, maxChars);
    Object.assign(merged, customFields);
  }

  const canonical = canonicalizeStructuredFields(merged);
  validateStructuredFieldsWithoutReserved(canonical, maxChars);
  return canonical;
}

function validateStructuredFieldsWithoutReserved(fields: StructuredFields, maxChars: number): void {
  const entries = Object.entries(fields);
  if (entries.length > MAX_STRUCTURED_FIELDS) {
    throw new Error(`Structured fields contain too many entries.`);
  }

  let totalCharacters = 0;
  for (const [key, value] of entries) {
    if (!STRUCTURED_FIELD_KEY_PATTERN.test(key)) {
      throw new Error(`Invalid structured field key: ${key}`);
    }
    if (typeof value !== 'string') {
      throw new Error(`Structured field value must be a string: ${key}`);
    }
    if (countSanitizedCharacters(value) > maxChars) {
      throw new Error(`Structured field value exceeds ${maxChars} characters: ${key}`);
    }
    totalCharacters += countSanitizedCharacters(value);
  }

  if (totalCharacters > maxChars) {
    throw new Error(`Structured fields exceed the ${maxChars} character limit.`);
  }
}

export function countStructuredCharacters(fields: StructuredFields): number {
  return Object.values(fields).reduce((total, value) => total + countSanitizedCharacters(value), 0);
}

export function isStructuredFields(value: unknown): value is StructuredFields {
  return !!value && typeof value === 'object' && !Array.isArray(value) &&
    Object.values(value).every((field) => typeof field === 'string');
}

export function splitStructuredTranslation(fields: StructuredFields): {
  translatedTitle?: string;
  translatedExcerpt?: string;
  translatedContent?: string;
  translatedFields: StructuredFields;
} {
  const translatedFields: StructuredFields = {};
  for (const [key, value] of Object.entries(fields)) {
    if (key !== 'title' && key !== 'excerpt' && key !== 'content') {
      translatedFields[key] = value;
    }
  }

  return {
    translatedTitle: fields.title,
    translatedExcerpt: fields.excerpt,
    translatedContent: fields.content,
    translatedFields: canonicalizeStructuredFields(translatedFields),
  };
}
