export interface DerivedCacheIdentity {
  readonly sourceFingerprint: string;
  readonly operation: string;
  readonly streamIndex?: number;
  readonly settings?: Readonly<Record<string, unknown>>;
  readonly version?: number;
}

export function createSourceFingerprint(
  file: Pick<File, 'name' | 'size' | 'lastModified'>
): string {
  return stableHash({
    name: file.name,
    size: file.size,
    lastModified: file.lastModified,
  });
}

export function createCacheKey(identity: DerivedCacheIdentity): string {
  if (
    !identity.sourceFingerprint.trim() ||
    !/^[a-z0-9._-]+$/iu.test(identity.operation)
  ) {
    throw new TypeError(
      'Cache identity requires a fingerprint and safe operation'
    );
  }
  return `${identity.operation}-${stableHash({
    sourceFingerprint: identity.sourceFingerprint,
    streamIndex: identity.streamIndex ?? null,
    settings: identity.settings ?? {},
    version: identity.version ?? 1,
  })}`;
}

export function stableStringify(value: unknown): string {
  return JSON.stringify(normalize(value));
}

export function stableHash(value: unknown): string {
  const text = stableStringify(value);
  let high = 0xcbf29ce4;
  let low = 0x84222325;
  for (let index = 0; index < text.length; index += 1) {
    low ^= text.charCodeAt(index);
    const lowProduct = low * 0x1b3;
    const carry = Math.floor(lowProduct / 0x1_0000_0000);
    low = lowProduct >>> 0;
    high = (high * 0x1b3 + carry) >>> 0;
  }
  return `${high.toString(16).padStart(8, '0')}${low
    .toString(16)
    .padStart(8, '0')}`;
}

function normalize(value: unknown): unknown {
  if (
    value === null ||
    typeof value === 'string' ||
    typeof value === 'boolean'
  ) {
    return value;
  }
  if (typeof value === 'number') {
    if (!Number.isFinite(value)) {
      throw new TypeError('Cache settings cannot contain non-finite numbers');
    }
    return Object.is(value, -0) ? 0 : value;
  }
  if (Array.isArray(value)) {
    return value.map(normalize);
  }
  if (typeof value === 'object') {
    const result: Record<string, unknown> = {};
    for (const key of Object.keys(value as Record<string, unknown>).sort()) {
      const entry = (value as Record<string, unknown>)[key];
      if (entry !== undefined) {
        result[key] = normalize(entry);
      }
    }
    return result;
  }
  throw new TypeError(
    'Cache settings must contain only JSON-compatible values'
  );
}
