// lib/trace.js
const { AsyncLocalStorage } = require('node:async_hooks');
const { randomUUID } = require('node:crypto');
const { logSh } = require('./ErrorReporting');

const als = new AsyncLocalStorage();

function now() { return performance.now(); }
function dur(ms) {
  if (ms < 1e3) return `${ms.toFixed(1)}ms`;
  const s = ms / 1e3;
  return s < 60 ? `${s.toFixed(2)}s` : `${(s/60).toFixed(2)}m`;
}

class Span {
  constructor({ name, parent = null, attrs = {} }) {
    this.id = randomUUID();
    this.name = name;
    this.parent = parent;
    this.children = [];
    this.attrs = attrs;
    this.start = now();
    this.end = null;
    this.status = 'ok';
    this.error = null;
  }
  pathNames() {
    const names = [];
    let cur = this;
    while (cur) { names.unshift(cur.name); cur = cur.parent; }
    return names.join(' > ');
  }
  finish() { this.end = now(); }
  duration() { return (this.end ?? now()) - this.start; }
}

class Tracer {
  constructor() {
    this.rootSpans = [];
  }
  current() { return als.getStore(); }

  async startSpan(name, attrs = {}) {
    const parent = this.current();
    const span = new Span({ name, parent, attrs });
    if (parent) parent.children.push(span);
    else this.rootSpans.push(span);
    
    // Formater les paramètres pour affichage
    const paramsStr = this.formatParams(attrs);
    await logSh(`▶ ${name}${paramsStr}`, 'TRACE');
    return span;
  }

  async run(name, fn, attrs = {}) {
    const parent = this.current();
    const span = await this.startSpan(name, attrs);
    return await als.run(span, async () => {
      try {
        const res = await fn();
        span.finish();
        const paramsStr = this.formatParams(span.attrs);
        await logSh(`✔ ${name}${paramsStr} (${dur(span.duration())})`, 'TRACE');
        return res;
      } catch (err) {
        span.status = 'error';
        span.error = { message: err?.message, stack: err?.stack };
        span.finish();
        const paramsStr = this.formatParams(span.attrs);
        await logSh(`✖ ${name}${paramsStr} FAILED (${dur(span.duration())})`, 'ERROR');
        await logSh(`Stack trace: ${span.error.message}`, 'ERROR');
        if (span.error.stack) {
          const stackLines = span.error.stack.split('\n').slice(1, 6); // Première 5 lignes du stack
          for (const line of stackLines) {
            await logSh(`  ${line.trim()}`, 'ERROR');
          }
        }
        throw err;
      }
    });
  }

  async event(msg, extra = {}) {
    const span = this.current();
    const data = { trace: true, evt: 'span.event', ...extra };
    if (span) {
      data.span = span.id;
      data.path = span.pathNames();
      data.since_ms = +( (now() - span.start).toFixed(1) );
    }
    await logSh(`• ${msg}`, 'TRACE');
  }

  async annotate(fields = {}) {
    const span = this.current();
    if (span) Object.assign(span.attrs, fields);
    await logSh('… annotate', 'TRACE');
  }

  formatParams(attrs = {}) {
    const params = Object.entries(attrs)
      .filter(([key, value]) => value !== undefined && value !== null)
      .map(([key, value]) => {
        // Tronquer les valeurs trop longues
        const strValue = String(value);
        const truncated = strValue.length > 50 ? strValue.substring(0, 47) + '...' : strValue;
        return `${key}=${truncated}`;
      });
    
    return params.length > 0 ? `(${params.join(', ')})` : '';
  }

  printSummary() {
    const lines = [];
    const draw = (node, depth = 0) => {
      const pad = '  '.repeat(depth);
      const icon = node.status === 'error' ? '✖' : '✔';
      lines.push(`${pad}${icon} ${node.name}  (${dur(node.duration())})`);
      if (Object.keys(node.attrs ?? {}).length) {
        lines.push(`${pad}   attrs: ${JSON.stringify(node.attrs)}`);
      }
      for (const ch of node.children) draw(ch, depth + 1);
      if (node.status === 'error' && node.error?.message) {
        lines.push(`${pad}   error: ${node.error.message}`);
        if (node.error.stack) {
          const stackLines = String(node.error.stack || '').split('\n').slice(1, 4).map(s => s.trim());
          if (stackLines.length) {
            lines.push(`${pad}   stack:`);
            stackLines.forEach(line => {
              if (line) lines.push(`${pad}     ${line}`);
            });
          }
        }
      }
    };
    for (const r of this.rootSpans) draw(r, 0);
    const summary = lines.join('\n');
    logSh(`\n—— TRACE SUMMARY ——\n${summary}\n—— END TRACE ——`, 'INFO');
    return summary;
  }
}

const tracer = new Tracer();

function setupTracer(moduleName = 'Default') {
  return {
    run: (name, fn, params = {}) => tracer.run(name, fn, params)
  };
}

module.exports = {
  Span,
  Tracer,
  tracer,
  setupTracer
};