const CONTROL_OR_UNSAFE =
  /[\u0000-\u001f\u007f-\u009f\u200b-\u200f\u202a-\u202e\u2060-\u206f\ufeff]/u;
const YAML_UNSAFE_PROPERTY_KEY = /[:#{}\[\],&*?|>!%@`'"]/u;
const SECRET_PROPERTY =
  /(?:^|[-_.])(api[-_]?key|credential|password|secret|token)(?:$|[-_.])/iu;
const INTERNAL_PROPERTY_PREFIXES = Object.freeze([
  "calendar",
  "sync",
  "share",
  "provider",
  "thortiq",
]);

export const MAX_SCHEMA_IDENTIFIER_CHARACTERS = 128;

export const PROTECTED_OUTPUT_PROPERTIES = Object.freeze([
  "id",
  "title",
  "created",
  "updated",
  "private",
  "tags",
  "filename",
  "subdir",
  "drawingFile",
]);

const PROTECTED_OUTPUT_PROPERTY_SET = new Set(PROTECTED_OUTPUT_PROPERTIES);

function isCanonicalIdentifier(value: unknown): value is string {
  return typeof value === "string"
    && value.length > 0
    && Array.from(value).length <= MAX_SCHEMA_IDENTIFIER_CHARACTERS
    && value === value.normalize("NFC")
    && value.trim() === value
    && !CONTROL_OR_UNSAFE.test(value);
}

export function isCsvHeaderIdentifier(value: unknown): value is string {
  return isCanonicalIdentifier(value) && !value.includes("=");
}

export function isReadablePropertyIdentifier(value: unknown): value is string {
  if (!isCanonicalIdentifier(value) || YAML_UNSAFE_PROPERTY_KEY.test(value)) {
    return false;
  }
  const lower = value.toLocaleLowerCase("en-US");
  const segmented = value.replace(/([a-z0-9])([A-Z])/gu, "$1-$2");
  return !SECRET_PROPERTY.test(segmented)
    && !INTERNAL_PROPERTY_PREFIXES.some(prefix => lower.startsWith(prefix));
}

export function isMutablePropertyIdentifier(value: unknown): value is string {
  return isReadablePropertyIdentifier(value)
    && !PROTECTED_OUTPUT_PROPERTY_SET.has(value);
}

export function sameIdentifier(left: string, right: string): boolean {
  return left.toLocaleLowerCase("en-US") === right.toLocaleLowerCase("en-US");
}
