'use strict';

var chunkOLISEQHS_cjs = require('./chunk-OLISEQHS.cjs');
var lib = require('@djangocfg/ui-core/lib');
var lucideReact = require('lucide-react');
var jsxRuntime = require('react/jsx-runtime');
var react = require('react');
var consola = require('consola');
var zustand = require('zustand');
var middleware = require('zustand/middleware');
var i18n = require('@djangocfg/i18n');

var SIZE_CLS = {
  sm: "h-8 w-8 [&_svg]:h-4 [&_svg]:w-4",
  md: "h-10 w-10 [&_svg]:h-5 [&_svg]:w-5",
  lg: "h-12 w-12 [&_svg]:h-6 [&_svg]:w-6"
};
function DictationButton({
  status,
  onClick,
  isSupported = true,
  size = "md",
  className,
  style,
  ariaLabel,
  idleIcon,
  listeningIcon,
  disabled
}) {
  const listening = status === "listening" || status === "starting";
  const stopping = status === "stopping";
  const off = !isSupported;
  return /* @__PURE__ */ jsxRuntime.jsxs(
    "button",
    {
      type: "button",
      onClick,
      disabled: disabled || off,
      "aria-pressed": listening,
      "aria-label": ariaLabel ?? (listening ? "Stop dictation" : off ? "Dictation not supported" : "Start dictation"),
      className: lib.cn(
        "relative inline-flex items-center justify-center rounded-full transition-colors",
        "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
        "disabled:cursor-not-allowed disabled:opacity-50",
        SIZE_CLS[size],
        listening ? "bg-destructive text-destructive-foreground hover:bg-destructive/90" : "bg-primary text-primary-foreground hover:bg-primary/90",
        className
      ),
      style,
      children: [
        listening && /* @__PURE__ */ jsxRuntime.jsx(
          "span",
          {
            "aria-hidden": true,
            className: "absolute inset-0 rounded-full bg-destructive/40 animate-ping"
          }
        ),
        stopping ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "animate-spin" }) : off ? listeningIcon ?? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOff, {}) : listening ? listeningIcon ?? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, {}) : idleIcon ?? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, {})
      ]
    }
  );
}
chunkOLISEQHS_cjs.__name(DictationButton, "DictationButton");
var FRIENDLY = {
  unsupported: "Speech recognition isn't available in this browser.",
  "permission-denied": "Microphone access was denied. Allow it in your browser settings to dictate.",
  "no-microphone": "No microphone found.",
  network: "Network error talking to the speech service.",
  aborted: "Recognition was interrupted.",
  "no-speech": "No speech was detected. Try again.",
  language: "The requested language isn't supported by the engine.",
  engine: "The speech engine reported an error.",
  unknown: "Something went wrong with the microphone."
};
function ErrorBanner({ error, className, onDismiss }) {
  if (!error) return null;
  return /* @__PURE__ */ jsxRuntime.jsxs(
    "div",
    {
      role: "alert",
      className: lib.cn(
        "flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive",
        className
      ),
      children: [
        /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
        /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: FRIENDLY[error.code] ?? error.message }),
        onDismiss && /* @__PURE__ */ jsxRuntime.jsx(
          "button",
          {
            type: "button",
            onClick: onDismiss,
            className: "text-destructive hover:underline",
            children: "Dismiss"
          }
        )
      ]
    }
  );
}
chunkOLISEQHS_cjs.__name(ErrorBanner, "ErrorBanner");
function MicMeter({
  level,
  bars = 12,
  gap = 2,
  barWidth = 3,
  height = 24,
  className
}) {
  const clamped = Math.min(1, Math.max(0, level));
  return /* @__PURE__ */ jsxRuntime.jsx(
    "div",
    {
      role: "meter",
      "aria-valuemin": 0,
      "aria-valuemax": 1,
      "aria-valuenow": clamped,
      className: lib.cn("inline-flex items-center", className),
      style: { gap, height },
      children: Array.from({ length: bars }).map((_, i) => {
        const ratio = (i + 1) / bars;
        const active = clamped >= ratio - 0.5 / bars;
        const heightRatio = 1 - Math.abs(i - (bars - 1) / 2) / ((bars - 1) / 2 || 1);
        const barH = Math.max(2, height * (0.35 + heightRatio * 0.65));
        return /* @__PURE__ */ jsxRuntime.jsx(
          "span",
          {
            className: lib.cn(
              "rounded-sm transition-colors",
              active ? "bg-primary" : "bg-border"
            ),
            style: { width: barWidth, height: barH }
          },
          i
        );
      })
    }
  );
}
chunkOLISEQHS_cjs.__name(MicMeter, "MicMeter");
function PushToTalkHint({ chord, className }) {
  const formatted = chord.split("+").map((p) => p.trim().toLowerCase()).map((p) => {
    switch (p) {
      case "mod":
      case "meta":
        return "\u2318";
      case "alt":
        return "\u2325";
      case "shift":
        return "\u21E7";
      case "ctrl":
        return "\u2303";
      default:
        return p.length === 1 ? p.toUpperCase() : p;
    }
  }).join("");
  return /* @__PURE__ */ jsxRuntime.jsxs(
    "span",
    {
      className: lib.cn(
        "inline-flex items-center gap-1 text-[11px] text-muted-foreground",
        className
      ),
      children: [
        "Hold",
        /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px] text-foreground", children: formatted }),
        "to talk"
      ]
    }
  );
}
chunkOLISEQHS_cjs.__name(PushToTalkHint, "PushToTalkHint");

// src/tools/SpeechRecognition/core/transcript.ts
var EMPTY_TRANSCRIPT = {
  interim: "",
  final: "",
  segments: []
};
function joinFinal(segments) {
  let out = "";
  for (const seg of segments) {
    if (!seg.isFinal) continue;
    const text = seg.text.trim();
    if (!text) continue;
    out = out ? `${out} ${text}` : text;
  }
  return out;
}
chunkOLISEQHS_cjs.__name(joinFinal, "joinFinal");
function buildTranscript(segments) {
  const last = segments[segments.length - 1];
  const interim = last && !last.isFinal ? last.text : "";
  return {
    interim,
    final: joinFinal(segments),
    segments
  };
}
chunkOLISEQHS_cjs.__name(buildTranscript, "buildTranscript");
function normaliseFinal(text) {
  return text.replace(/\s+/g, " ").replace(/\s+([,.!?])/g, "$1").trim();
}
chunkOLISEQHS_cjs.__name(normaliseFinal, "normaliseFinal");

// src/tools/SpeechRecognition/core/ids.ts
var counter = 0;
function newSegmentId() {
  counter = (counter + 1) % Number.MAX_SAFE_INTEGER;
  return `seg_${Date.now().toString(36)}_${counter.toString(36)}`;
}
chunkOLISEQHS_cjs.__name(newSegmentId, "newSegmentId");
var SCOPES = [
  "engine",
  "dictation",
  "slot",
  "composer",
  "mic",
  "push",
  "error"
];
var STORAGE_KEY = "djangocfg:speech-debug";
function readStorageFlag() {
  if (!lib.isBrowser) return false;
  try {
    return window.localStorage.getItem(STORAGE_KEY) === "1";
  } catch {
    return false;
  }
}
chunkOLISEQHS_cjs.__name(readStorageFlag, "readStorageFlag");
var cache = /* @__PURE__ */ new Map();
function buildLogger(enabled) {
  const root = consola.consola.withTag("speech");
  const subs = Object.fromEntries(
    SCOPES.map((scope) => [scope, root.withTag(scope)])
  );
  if (!enabled) {
    for (const scope of SCOPES) {
      if (scope === "error") continue;
      subs[scope].level = -999;
    }
  }
  return { ...subs, enabled };
}
chunkOLISEQHS_cjs.__name(buildLogger, "buildLogger");
function getSpeechLogger(debug) {
  const enabled = debug ?? (lib.isDev || readStorageFlag());
  let logger = cache.get(enabled);
  if (!logger) {
    logger = buildLogger(enabled);
    cache.set(enabled, logger);
  }
  return logger;
}
chunkOLISEQHS_cjs.__name(getSpeechLogger, "getSpeechLogger");
var sttLogger = consola.consola.withTag("ui-tools:speech");

// src/tools/SpeechRecognition/core/reducer.ts
var INITIAL_STATE = {
  status: "idle",
  segments: [],
  error: null,
  startedAt: null
};
function nowSinceStart(state) {
  return state.startedAt ? Date.now() - state.startedAt : 0;
}
chunkOLISEQHS_cjs.__name(nowSinceStart, "nowSinceStart");
function upsertSegment(segments, patch) {
  const idx = segments.findIndex((s) => s.id === patch.id);
  if (idx === -1) return [...segments, patch];
  const next = segments.slice();
  next[idx] = { ...next[idx], ...patch };
  return next;
}
chunkOLISEQHS_cjs.__name(upsertSegment, "upsertSegment");
function reducer(state, action) {
  switch (action.type) {
    case "START":
      return {
        ...state,
        status: "starting",
        error: null,
        startedAt: Date.now()
      };
    case "STARTED":
      return { ...state, status: "listening" };
    case "STOP":
      return { ...state, status: "stopping" };
    case "STOPPED":
    case "ABORT":
      return { ...state, status: "idle" };
    case "PARTIAL": {
      const seg = {
        id: action.segmentId,
        text: action.text,
        isFinal: false,
        confidence: action.confidence,
        startedAt: nowSinceStart(state)
      };
      return { ...state, segments: upsertSegment(state.segments, seg) };
    }
    case "FINAL": {
      const seg = {
        id: action.segmentId || newSegmentId(),
        text: action.text,
        isFinal: true,
        confidence: action.confidence,
        startedAt: nowSinceStart(state),
        endedAt: nowSinceStart(state)
      };
      return { ...state, segments: upsertSegment(state.segments, seg) };
    }
    case "ERROR":
      return { ...state, status: "error", error: action.error };
    case "RESET":
      return { ...INITIAL_STATE };
    default:
      return state;
  }
}
chunkOLISEQHS_cjs.__name(reducer, "reducer");

// src/tools/SpeechRecognition/core/engine/index.ts
function createEngineBus() {
  const listeners = {
    partial: /* @__PURE__ */ new Set(),
    final: /* @__PURE__ */ new Set(),
    error: /* @__PURE__ */ new Set(),
    state: /* @__PURE__ */ new Set()
  };
  return {
    on(event, cb) {
      const set = listeners[event];
      set.add(cb);
      return () => {
        set.delete(cb);
      };
    },
    emit(event, ...args) {
      const set = listeners[event];
      for (const cb of set) {
        try {
          cb(...args);
        } catch {
        }
      }
    },
    clear() {
      for (const key of Object.keys(listeners)) {
        listeners[key].clear();
      }
    }
  };
}
chunkOLISEQHS_cjs.__name(createEngineBus, "createEngineBus");

// src/tools/SpeechRecognition/core/engine/webspeech.ts
function resolveCtor() {
  if (typeof window === "undefined") return null;
  const w = window;
  return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
}
chunkOLISEQHS_cjs.__name(resolveCtor, "resolveCtor");
var ERROR_MAP = {
  "no-speech": "no-speech",
  aborted: "aborted",
  "audio-capture": "no-microphone",
  network: "network",
  "not-allowed": "permission-denied",
  "service-not-allowed": "permission-denied",
  "bad-grammar": "engine",
  "language-not-supported": "language"
};
function createWebSpeechEngine(opts = {}) {
  const Ctor = resolveCtor();
  const bus = createEngineBus();
  let instance = null;
  let currentSegmentId = null;
  function teardown() {
    if (!instance) return;
    instance.onresult = null;
    instance.onerror = null;
    instance.onstart = null;
    instance.onend = null;
    instance = null;
    currentSegmentId = null;
  }
  chunkOLISEQHS_cjs.__name(teardown, "teardown");
  return {
    id: "webspeech",
    isSupported: Ctor !== null,
    on(event, cb) {
      return bus.on(event, cb);
    },
    async start(start) {
      if (!Ctor) {
        const err = {
          code: "unsupported",
          message: "Web Speech API is not available in this browser."
        };
        bus.emit("error", err);
        throw err;
      }
      if (instance) {
        sttLogger.debug("[webspeech] start() called while running \u2014 ignoring");
        return;
      }
      bus.emit("state", "connecting");
      const rec = new Ctor();
      rec.lang = start.language;
      rec.interimResults = start.interim;
      rec.continuous = opts.continuous ?? true;
      rec.maxAlternatives = opts.maxAlternatives ?? 1;
      rec.onstart = () => {
        bus.emit("state", "listening");
      };
      rec.onend = () => {
        bus.emit("state", "closed");
        teardown();
      };
      rec.onerror = (e) => {
        const code = ERROR_MAP[e.error] ?? "engine";
        const err = {
          code,
          message: e.message || `Web Speech error: ${e.error}`
        };
        bus.emit("error", err);
      };
      rec.onresult = (e) => {
        for (let i = e.resultIndex; i < e.results.length; i += 1) {
          const res = e.results[i];
          const alt = res[0];
          const text = alt.transcript;
          if (!currentSegmentId) currentSegmentId = newSegmentId();
          if (res.isFinal) {
            bus.emit("final", text, currentSegmentId, alt.confidence);
            currentSegmentId = null;
          } else {
            bus.emit("partial", text, currentSegmentId);
          }
        }
      };
      if (start.signal) {
        start.signal.addEventListener("abort", () => {
          rec.abort();
        });
      }
      instance = rec;
      try {
        rec.start();
      } catch (cause) {
        const err = {
          code: "engine",
          message: "Failed to start Web Speech recognition.",
          cause
        };
        bus.emit("error", err);
        teardown();
        throw err;
      }
    },
    async stop() {
      if (!instance) return;
      bus.emit("state", "closing");
      instance.stop();
    },
    abort() {
      if (!instance) return;
      instance.abort();
    }
  };
}
chunkOLISEQHS_cjs.__name(createWebSpeechEngine, "createWebSpeechEngine");
var DEFAULTS = {
  language: null,
  deviceId: null,
  engineId: null,
  earcons: false
};
var useSpeechPrefs = zustand.create()(
  middleware.persist(
    (set) => ({
      ...DEFAULTS,
      setLanguage: /* @__PURE__ */ chunkOLISEQHS_cjs.__name((language) => set({ language }), "setLanguage"),
      setDeviceId: /* @__PURE__ */ chunkOLISEQHS_cjs.__name((deviceId) => set({ deviceId }), "setDeviceId"),
      setEngineId: /* @__PURE__ */ chunkOLISEQHS_cjs.__name((engineId) => set({ engineId }), "setEngineId"),
      setEarcons: /* @__PURE__ */ chunkOLISEQHS_cjs.__name((earcons) => set({ earcons }), "setEarcons"),
      reset: /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => set({ ...DEFAULTS }), "reset")
    }),
    {
      name: "djangocfg-stt:prefs",
      storage: middleware.createJSONStorage(
        () => typeof window === "undefined" ? void 0 : window.localStorage
      )
    }
  )
);
function useMicLevel(stream) {
  const [level, setLevel] = react.useState(0);
  const raf = react.useRef(null);
  react.useEffect(() => {
    if (!stream) {
      setLevel(0);
      return void 0;
    }
    const AC = window.AudioContext ?? window.webkitAudioContext;
    if (!AC) return void 0;
    const ctx = new AC();
    const source = ctx.createMediaStreamSource(stream);
    const analyser = ctx.createAnalyser();
    analyser.fftSize = 1024;
    analyser.smoothingTimeConstant = 0.7;
    source.connect(analyser);
    const buf = new Float32Array(analyser.fftSize);
    const tick = /* @__PURE__ */ chunkOLISEQHS_cjs.__name(() => {
      analyser.getFloatTimeDomainData(buf);
      let sum = 0;
      for (let i = 0; i < buf.length; i += 1) sum += buf[i] * buf[i];
      const rms = Math.sqrt(sum / buf.length);
      setLevel(Math.min(1, rms * 2.5));
      raf.current = requestAnimationFrame(tick);
    }, "tick");
    tick();
    return () => {
      if (raf.current != null) cancelAnimationFrame(raf.current);
      raf.current = null;
      source.disconnect();
      analyser.disconnect();
      void ctx.close();
    };
  }, [stream]);
  return level;
}
chunkOLISEQHS_cjs.__name(useMicLevel, "useMicLevel");

// src/tools/SpeechRecognition/core/language.ts
var ISO_TO_BCP47 = {
  en: "en-US",
  ru: "ru-RU",
  ko: "ko-KR",
  ja: "ja-JP",
  zh: "zh-CN",
  de: "de-DE",
  fr: "fr-FR",
  it: "it-IT",
  es: "es-ES",
  nl: "nl-NL",
  ar: "ar-SA",
  tr: "tr-TR",
  pl: "pl-PL",
  sv: "sv-SE",
  no: "nb-NO",
  da: "da-DK",
  pt: "pt-BR"
};
function toBCP47(code, table = ISO_TO_BCP47) {
  if (!code) return void 0;
  const trimmed = code.trim();
  if (!trimmed) return void 0;
  if (trimmed.includes("-")) return trimmed;
  const lower = trimmed.toLowerCase();
  return table[lower] ?? `${lower}-${lower.toUpperCase()}`;
}
chunkOLISEQHS_cjs.__name(toBCP47, "toBCP47");
function resolveSpeechLanguage(opts) {
  return toBCP47(opts.explicit) ?? toBCP47(opts.prefs) ?? toBCP47(opts.i18n) ?? toBCP47(typeof navigator !== "undefined" ? navigator.language : null) ?? "en-US";
}
chunkOLISEQHS_cjs.__name(resolveSpeechLanguage, "resolveSpeechLanguage");

// src/tools/SpeechRecognition/hooks/useResolvedLanguage.ts
function useResolvedLanguage(explicit) {
  const prefs = useSpeechPrefs();
  const locale = i18n.useLocaleOptional();
  return resolveSpeechLanguage({
    explicit,
    prefs: prefs.language,
    i18n: locale
  });
}
chunkOLISEQHS_cjs.__name(useResolvedLanguage, "useResolvedLanguage");

// src/tools/SpeechRecognition/hooks/useSpeechRecognition.ts
var log = getSpeechLogger();
function useSpeechRecognition(config = {}) {
  const prefs = useSpeechPrefs();
  const language = useResolvedLanguage(config.language);
  const engine = react.useMemo(
    () => config.engine ?? createWebSpeechEngine(),
    [config.engine]
  );
  const [state, dispatch] = react.useReducer(reducer, INITIAL_STATE);
  const [stream, setStream] = react.useState(null);
  const level = useMicLevel(stream);
  const cbRef = react.useRef(config);
  cbRef.current = config;
  react.useEffect(() => {
    log.engine.debug("subscribe", { engineId: engine.id });
    const offs = [
      engine.on("partial", (text, segmentId) => {
        log.engine.debug("partial", { len: text.length, segmentId });
        dispatch({ type: "PARTIAL", text, segmentId });
        const seg = {
          id: segmentId,
          text,
          isFinal: false,
          startedAt: Date.now()
        };
        cbRef.current.onPartial?.(text, seg);
      }),
      engine.on("final", (text, segmentId, confidence) => {
        log.engine.info("final", {
          len: text.length,
          segmentId,
          confidence
        });
        dispatch({ type: "FINAL", text, segmentId, confidence });
        const seg = {
          id: segmentId,
          text,
          isFinal: true,
          confidence,
          startedAt: Date.now(),
          endedAt: Date.now()
        };
        cbRef.current.onFinal?.(text, seg);
      }),
      engine.on("error", (err) => {
        log.error.error("engine error", err);
        dispatch({ type: "ERROR", error: err });
        cbRef.current.onError?.(err);
      }),
      engine.on("state", (s) => {
        log.engine.debug("state", s);
        if (s === "listening") {
          dispatch({ type: "STARTED" });
          cbRef.current.onStart?.();
          setStream(engine.getStream?.() ?? null);
        } else if (s === "closed") {
          dispatch({ type: "STOPPED" });
          cbRef.current.onStop?.();
          setStream(null);
        }
      })
    ];
    return () => {
      offs.forEach((off) => off());
    };
  }, [engine]);
  const silenceTimer = react.useRef(null);
  const maxTimer = react.useRef(null);
  react.useEffect(() => {
    if (state.status !== "listening") return void 0;
    const { silenceMs, maxMs, silenceThreshold = 0.02 } = config.autoStop ?? {};
    if (maxMs) {
      maxTimer.current = window.setTimeout(() => {
        log.engine.debug("autoStop max duration hit");
        void engine.stop();
      }, maxMs);
    }
    if (silenceMs) {
      const checkInterval = window.setInterval(() => {
        if (level < silenceThreshold) {
          if (silenceTimer.current == null) {
            silenceTimer.current = window.setTimeout(() => {
              log.engine.debug("autoStop silence detected");
              void engine.stop();
            }, silenceMs);
          }
        } else if (silenceTimer.current != null) {
          clearTimeout(silenceTimer.current);
          silenceTimer.current = null;
        }
      }, 200);
      return () => {
        clearInterval(checkInterval);
        if (silenceTimer.current != null) clearTimeout(silenceTimer.current);
        silenceTimer.current = null;
        if (maxTimer.current != null) clearTimeout(maxTimer.current);
        maxTimer.current = null;
      };
    }
    return () => {
      if (maxTimer.current != null) clearTimeout(maxTimer.current);
      maxTimer.current = null;
    };
  }, [state.status, config.autoStop, level, engine]);
  const start = react.useCallback(async () => {
    if (state.status === "listening" || state.status === "starting") return;
    dispatch({ type: "START" });
    try {
      await engine.start({
        language,
        interim: config.interim ?? true,
        deviceId: config.deviceId ?? prefs.deviceId ?? void 0
      });
    } catch (cause) {
      log.engine.warn("start threw", cause);
    }
  }, [engine, language, config.interim, config.deviceId, prefs.deviceId, state.status]);
  const stop = react.useCallback(async () => {
    if (state.status === "idle" || state.status === "stopping") return;
    dispatch({ type: "STOP" });
    await engine.stop();
  }, [engine, state.status]);
  const abort = react.useCallback(() => {
    engine.abort();
    dispatch({ type: "ABORT" });
  }, [engine]);
  const toggle = react.useCallback(async () => {
    if (state.status === "listening" || state.status === "starting") {
      await stop();
    } else {
      await start();
    }
  }, [state.status, start, stop]);
  const reset = react.useCallback(() => {
    dispatch({ type: "RESET" });
  }, []);
  const transcript = react.useMemo(
    () => state.segments.length === 0 ? EMPTY_TRANSCRIPT : buildTranscript(state.segments),
    [state.segments]
  );
  return {
    status: state.status,
    isSupported: engine.isSupported,
    transcript,
    error: state.error,
    level,
    start,
    stop,
    abort,
    toggle,
    reset
  };
}
chunkOLISEQHS_cjs.__name(useSpeechRecognition, "useSpeechRecognition");

// src/tools/SpeechRecognition/hooks/useDictation.ts
function useDictation(config) {
  const { value, onChange, separator = " ", ...rest } = config;
  const valueRef = react.useRef(value);
  valueRef.current = value;
  const onChangeRef = react.useRef(onChange);
  onChangeRef.current = onChange;
  const rec = useSpeechRecognition({
    ...rest,
    onFinal: /* @__PURE__ */ chunkOLISEQHS_cjs.__name((text) => {
      const clean = normaliseFinal(text);
      if (!clean) return;
      const prev = valueRef.current;
      const next = prev ? `${prev}${separator}${clean}` : clean;
      onChangeRef.current(next);
    }, "onFinal")
  });
  react.useEffect(() => {
    valueRef.current = value;
  }, [value]);
  return {
    ...rec,
    toggleDictation: rec.toggle
  };
}
chunkOLISEQHS_cjs.__name(useDictation, "useDictation");
var MOD_KEYS = /* @__PURE__ */ new Set(["shift", "ctrl", "alt", "meta", "mod"]);
function parseChord(chord) {
  const parts = chord.toLowerCase().split("+").map((s) => s.trim());
  const mods = /* @__PURE__ */ new Set();
  let main = null;
  for (const part of parts) {
    if (MOD_KEYS.has(part)) {
      mods.add(part === "mod" ? "meta" : part);
    } else {
      main = part;
    }
  }
  return { mods, main };
}
chunkOLISEQHS_cjs.__name(parseChord, "parseChord");
function matches(e, mods, main) {
  if (mods.has("shift") !== e.shiftKey) return false;
  if (mods.has("ctrl") !== e.ctrlKey) return false;
  if (mods.has("alt") !== e.altKey) return false;
  if (mods.has("meta") !== (e.metaKey || !e.metaKey && false)) return false;
  if (main && e.key.toLowerCase() !== main) return false;
  return true;
}
chunkOLISEQHS_cjs.__name(matches, "matches");
function usePushToTalk(recognition, opts) {
  const { key, enabled = true } = opts;
  react.useEffect(() => {
    if (!enabled || typeof window === "undefined") return void 0;
    const { mods, main } = parseChord(key);
    const onDown = /* @__PURE__ */ chunkOLISEQHS_cjs.__name((e) => {
      if (e.repeat) return;
      if (!main && mods.size === 0) return;
      const target = e.target;
      const inField = target?.tagName === "INPUT" || target?.tagName === "TEXTAREA" || target?.isContentEditable;
      if (inField && mods.size === 0) return;
      if (!matches(e, mods, main)) return;
      if (recognition.status === "listening" || recognition.status === "starting") return;
      e.preventDefault();
      void recognition.start();
    }, "onDown");
    const onUp = /* @__PURE__ */ chunkOLISEQHS_cjs.__name((e) => {
      if (!matches(e, mods, main) && main && e.key.toLowerCase() !== main) return;
      if (recognition.status !== "listening" && recognition.status !== "starting") return;
      void recognition.stop();
    }, "onUp");
    window.addEventListener("keydown", onDown);
    window.addEventListener("keyup", onUp);
    return () => {
      window.removeEventListener("keydown", onDown);
      window.removeEventListener("keyup", onUp);
    };
  }, [enabled, key, recognition]);
}
chunkOLISEQHS_cjs.__name(usePushToTalk, "usePushToTalk");
function DictationField({
  value,
  onChange,
  engine,
  language,
  pushToTalk,
  placeholder = "Type or press the mic to dictate\u2026",
  rows = 3,
  disabled,
  showInterim = true,
  showMeter = true,
  className,
  textareaClassName
}) {
  const rec = useDictation({
    value,
    onChange,
    engine,
    language
  });
  usePushToTalk(rec, {
    key: pushToTalk?.key ?? "alt",
    enabled: !!pushToTalk && pushToTalk.enabled !== false
  });
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("flex flex-col gap-2", className), children: [
    /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
      /* @__PURE__ */ jsxRuntime.jsx(
        "textarea",
        {
          value,
          onChange: (e) => onChange(e.target.value),
          placeholder,
          rows,
          disabled,
          className: lib.cn(
            "w-full resize-y rounded-md border border-input bg-background px-3 py-2 text-sm",
            "placeholder:text-muted-foreground",
            "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
            "disabled:cursor-not-allowed disabled:opacity-50",
            textareaClassName
          )
        }
      ),
      showInterim && rec.transcript.interim && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pointer-events-none mt-1 text-xs italic text-muted-foreground", children: [
        "\u2026 ",
        rec.transcript.interim
      ] })
    ] }),
    /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
      /* @__PURE__ */ jsxRuntime.jsx(
        DictationButton,
        {
          status: rec.status,
          isSupported: rec.isSupported,
          onClick: () => void rec.toggleDictation(),
          size: "sm",
          disabled
        }
      ),
      showMeter && /* @__PURE__ */ jsxRuntime.jsx(MicMeter, { level: rec.level, bars: 10, height: 20 }),
      pushToTalk && /* @__PURE__ */ jsxRuntime.jsx(PushToTalkHint, { chord: pushToTalk.key, className: "ml-auto" })
    ] }),
    /* @__PURE__ */ jsxRuntime.jsx(ErrorBanner, { error: rec.error })
  ] });
}
chunkOLISEQHS_cjs.__name(DictationField, "DictationField");

exports.DictationField = DictationField;
exports.getSpeechLogger = getSpeechLogger;
exports.useResolvedLanguage = useResolvedLanguage;
exports.useSpeechPrefs = useSpeechPrefs;
//# sourceMappingURL=chunk-ADEN3UA4.cjs.map
//# sourceMappingURL=chunk-ADEN3UA4.cjs.map