/** Brand Design Ltd. public read client. No credentials, orders or payments. */
export const VERSION = '1.0.1';
export const RESOURCES = Object.freeze({
  discover: 'https://branddesign.ltd/.well-known/agent-bootstrap.json',
  services: 'https://branddesign.ltd/services.json',
  pricing: 'https://branddesign.ltd/.well-known/pricing.json',
  evidence: 'https://branddesign.ltd/.well-known/brand-evidence.json',
  catalog: 'https://branddesign.host/commerce/v1/catalog',
  capabilities: 'https://branddesign.host/commerce/v1/manifest'
});
const ORIGINS = new Set(['https://branddesign.ltd', 'https://branddesign.host']);
const MAX_BYTES = 8 * 1024 * 1024;

export async function readPublic(url, { signal, format = 'json' } = {}) {
  const parsed = new URL(url);
  if (!ORIGINS.has(parsed.origin) || parsed.username || parsed.password || parsed.search || parsed.hash)
    throw new Error('Only canonical public Brand Design Ltd. resource URLs are allowed.');
  const known = Object.values(RESOURCES).includes(url) || [
    '/agent/memory_summary.md', '/MEMORY.md', '/AGENTS.md',
    '/skills/brand-design-commerce/SKILL.md', '/agent/SKILL.md', '/agent/context.md'
  ].some(path => url === 'https://branddesign.ltd' + path);
  if (!known) throw new Error('Resource is outside the public read allowlist.');
  const timeout = AbortSignal.timeout(15000);
  // Same-origin public GET bridge avoids requiring broader CSP/CORS permissions.
  const bridgeKey = typeof document !== 'undefined' ?
    Object.keys(RESOURCES).find(key => ['catalog','capabilities'].includes(key) && RESOURCES[key] === url) : null;
  const transport = bridgeKey ? 'https://branddesign.ltd/agent-commerce-read.php?resource=' + bridgeKey : url;
  const response = await fetch(transport, {
    method: 'GET', credentials: 'omit', redirect: 'error', cache: 'no-store',
    headers: { Accept: format === 'json' ? 'application/json' : 'text/markdown, text/plain' },
    signal: signal ? AbortSignal.any([signal, timeout]) : timeout
  });
  if (!response.ok) throw new Error('HTTP ' + response.status + ' for ' + url);
  const type = response.headers.get('content-type') || '';
  if (format === 'json' && !type.toLowerCase().includes('application/json'))
    throw new Error('Expected JSON for ' + url);
  const reader = response.body.getReader();
  const chunks = []; let size = 0;
  try {
    while (true) {
      const {done, value} = await reader.read();
      if (done) break;
      size += value.byteLength;
      if (size > MAX_BYTES) throw new Error('Public response exceeds 8 MiB.');
      chunks.push(value);
    }
  } finally { await reader.cancel(); }
  const bytes = new Uint8Array(size); let offset = 0;
  for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.length; }
  const text = new TextDecoder('utf-8', {fatal:true}).decode(bytes);
  return {url, retrievedAt: new Date().toISOString(), bytes,
    data: format === 'json' ? JSON.parse(text) : text};
}

export async function runRead(command, input = {}, options = {}) {
  if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('Input must be an object.');
  if (Object.keys(input).some(key => key !== 'id') || (command !== 'service' && Object.keys(input).length))
    throw new Error('Unexpected input field.');
  const url = command === 'service' ? RESOURCES.services : RESOURCES[command];
  if (!url) throw new Error('Unknown public read command.');
  if (command === 'service' && (typeof input.id !== 'string' || !/^[a-z0-9][a-z0-9-]{0,79}$/.test(input.id)))
    throw new Error('Provide a valid service id.');
  const result = await readPublic(url, options);
  let data = result.data;
  if (command === 'service') {
    const items = Array.isArray(data) ? data : data.services;
    if (!Array.isArray(items)) throw new Error('Service response has no services array.');
    data = items.find(item => item.id === input.id);
    if (!data) throw new Error('Unknown service id: ' + input.id);
  }
  return {clientVersion: VERSION, source: result.url, retrievedAt: result.retrievedAt,
    readOnly: true, authorityGranted: false, data};
}

export async function doctor() {
  const checks = [];
  let bootstrap;
  try { bootstrap = (await runRead('discover')).data; checks.push({resource:'bootstrap',ok:true}); }
  catch (error) { checks.push({resource:'bootstrap',ok:false,error:error.message}); }
  if (bootstrap) {
    for (const key of ['memorySummary','memory','policy','skill']) {
      const resource = bootstrap.resourceDigests?.[key];
      try {
        if (!resource || !/^[a-f0-9]{64}$/.test(resource.sha256)) throw new Error('Missing SHA-256 metadata.');
        const result = await readPublic(resource.url, {format:'text'});
        const digest = [...new Uint8Array(await crypto.subtle.digest('SHA-256',result.bytes))]
          .map(byte=>byte.toString(16).padStart(2,'0')).join('');
        if (digest !== resource.sha256) throw new Error('SHA-256 mismatch.');
        checks.push({resource:key,ok:true,sha256:digest});
      } catch (error) { checks.push({resource:key,ok:false,error:error.message}); }
    }
  }
  for (const resource of ['catalog','capabilities']) {
    try { await runRead(resource); checks.push({resource,ok:true}); }
    catch (error) { checks.push({resource,ok:false,error:error.message}); }
  }
  return {clientVersion:VERSION,ok:checks.every(check=>check.ok),readOnly:true,
    authorityGranted:false,checks,
    note:'Public retrieval and integrity checks only; MCP authentication, buyer authority, browser support and payments are not tested.'};
}
