// organ.el — Neuron's I/O organ, in El.
//
// THE PRINCIPLE. El speaks. The engram stores geometry and does not speak.
// Before this file the organ was a 939-line Swift program standing next to the
// language (peripheral/src/periph.swift): Neuron's mouth and ears were a
// separate binary, and "speak" meant "shell out to that binary, which shells
// out to afplay." That is not a voice, it is a subprocess. The voice belongs to
// the language and its runtime.
//
// THE SPLIT. Exactly two things here are not El, and they are the two things El
// cannot express as arithmetic:
//
//   the speaker  — handing a buffer to the DAC and waiting for it to drain
//   the capture  — asking the OS for samples off a mic or frames off a camera
//
// Those live in lang/runtime/el_audio_darwin.m and el_capture_darwin.m, as
// their own translation units, declared in el_runtime.h. Everything ELSE that
// the Swift did — WAV encode and decode, LPC autocorrelation, Levinson-Durbin,
// formant extraction off the all-pole envelope, source-filter resynthesis, the
// compact descriptors, the converse yield-or-hold decision — is arithmetic, and
// arithmetic is El's. See organ_dsp.el for that half.
//
// WHERE THE VOICE COMES FROM. Not from this file, and not from a JSON manifest
// on disk. A voice is GEOMETRY IN THE ENGRAM, and the organ goes and gets it by
// asking the engram, the same way anything else asks the engram for anything:
// a query against the graph, then read the numbers off the node that comes
// back. organ_voice_fetch is that. The previous path, load_voice("...json"),
// parsed a file — which quietly made the voice a build artifact instead of a
// memory. If the region is not in the graph, the honest answer is an empty
// result, not a default voice.
//
// WHAT THE ORGAN NEVER DOES. It never learns a word. Pronunciation, vocabulary
// and phonemes are the language faculty's, already built as ingested geometry —
// "the engram knows how to pronounce." The seam is synth_codes(codes, voice,
// pmap): the codes and the phoneme map arrive from the language side as
// geometry, and the organ's whole job is turning them into samples and getting
// the samples out the speaker, plus the same trip in reverse for the senses.
//
// RAILS, all non-negotiable:
//   own-core     — CoreAudio / AVFoundation / ImageIO, all shipped with the OS.
//                  No cloud, no model, no heavy dependency. There is no network
//                  call anywhere in the organ, by construction.
//   local-only   — raw streams stay on the machine. What leaves a capture is a
//                  DESCRIPTOR of a few dozen numbers, never the stream.
//   consent      — two locks on the sensitive senses: a Neuron-level grant AND
//                  the OS TCC permission. Camera and mic FAIL CLOSED without
//                  both. The speaker is disclosed but not gated (see below).
//   disclosed    — every device touch prints a [peripheral] line on stderr.
//                  Nothing here is ever silent about being a device.

// ── Disclosure ───────────────────────────────────────────────────────────────
//
// stderr, not stdout: a program that announces "I am opening the microphone" on
// stdout has corrupted its own output. And flushed immediately, so the line is
// on the terminal BEFORE the device is touched — a disclosure that arrives
// after the fact is a log, not a disclosure.

fn organ_disclose(msg: String) -> Bool {
    eprintln("  [peripheral] " + msg)
    return true
}

// ── Consent, the Neuron-level lock ───────────────────────────────────────────
//
// The OS has its own lock (TCC) and it is not enough on its own: TCC grants the
// TERMINAL access to the microphone, once, more or less forever. That says the
// user trusts the app. It does not say the user consents to THIS program
// listening THIS time. So Neuron keeps its own grant, revocable, on the same
// footing — and both must be open for a sensitive sense to work.
//
// Stored next to the organ rather than in the engram deliberately: consent must
// be inspectable and revocable without a running graph, and a permission that
// can only be revoked by the system it governs is not a permission.

fn organ_consent_path() -> String {
    let home: String = env("PERIPH_HOME")
    if str_eq(home, "") {
        return "peripheral/.consent.json"
    }
    return home + "/.consent.json"
}

fn organ_consent_granted(device: String) -> Bool {
    let raw: String = fs_read(organ_consent_path())
    if str_eq(raw, "") {
        return false
    }
    // A device is granted only on an explicit true. Anything unparseable,
    // missing or malformed reads as NOT granted — the failure direction for a
    // permission file is always closed.
    let key: String = "\"" + device + "\""
    let at: Int = str_index_of(raw, key)
    if at < 0 {
        return false
    }
    let tail: String = str_slice(raw, at, str_len(raw))
    let t: Int = str_index_of(tail, "true")
    let f: Int = str_index_of(tail, "false")
    if t < 0 {
        return false
    }
    if f < 0 {
        return true
    }
    // whichever token appears first after the key is this device's value
    if t < f {
        return true
    }
    return false
}

fn organ_consent_write(camera: Bool, mic: Bool) -> Bool {
    let c: String = "false"
    if camera {
        c = "true"
    }
    let m: String = "false"
    if mic {
        m = "true"
    }
    return fs_write(organ_consent_path(), "{\"camera\": " + c + ", \"mic\": " + m + "}\n")
}

fn organ_grant(device: String) -> Bool {
    let cam: Bool = organ_consent_granted("camera")
    let mic: Bool = organ_consent_granted("mic")
    if str_eq(device, "camera") {
        cam = true
    }
    if str_eq(device, "mic") {
        mic = true
    }
    let ok: Bool = organ_consent_write(cam, mic)
    organ_disclose("granted '" + device + "' (Neuron-level) — raw stream stays local, never egresses.")
    return ok
}

fn organ_revoke(device: String) -> Bool {
    let cam: Bool = organ_consent_granted("camera")
    let mic: Bool = organ_consent_granted("mic")
    if str_eq(device, "camera") {
        cam = false
    }
    if str_eq(device, "mic") {
        mic = false
    }
    let ok: Bool = organ_consent_write(cam, mic)
    organ_disclose("revoked '" + device + "' (Neuron-level).")
    return ok
}

fn organ_consent_status() -> String {
    let cam: String = "denied"
    if organ_consent_granted("camera") {
        cam = "granted"
    }
    let mic: String = "denied"
    if organ_consent_granted("mic") {
        mic = "granted"
    }
    return "camera=" + cam + " mic=" + mic
}

// Both locks, in order, with a disclosure for each outcome. Returns false and
// says exactly which lock is shut — a refusal that does not say why is
// indistinguishable from a bug.
fn organ_may_listen() -> Bool {
    if organ_consent_granted("mic") == false {
        organ_disclose("CONSENT DENIED for 'mic' (Neuron-level). Run: organ grant mic")
        return false
    }
    if mic_available() == 0 {
        organ_disclose("CONSENT DENIED for 'mic' (OS/TCC), or no input device. Grant microphone access to this terminal in System Settings > Privacy.")
        return false
    }
    organ_disclose("consent OK (Neuron + OS) for 'mic' — local only, never egresses.")
    return true
}

fn organ_may_see() -> Bool {
    if organ_consent_granted("camera") == false {
        organ_disclose("CONSENT DENIED for 'camera' (Neuron-level). Run: organ grant camera")
        return false
    }
    if camera_available() == 0 {
        organ_disclose("CONSENT DENIED for 'camera' (OS/TCC), or no capture device. Grant camera access to this terminal in System Settings > Privacy.")
        return false
    }
    organ_disclose("consent OK (Neuron + OS) for 'camera' — local only, never egresses.")
    return true
}

// ── SPEAKER (efferent) ───────────────────────────────────────────────────────
//
// Not consent-gated, and that is a deliberate asymmetry rather than an
// oversight. The microphone and camera take information OFF the user without
// them necessarily knowing; the speaker puts information INTO a room the user
// is in, audibly, which is self-disclosing by its nature — you cannot secretly
// speak aloud. So the speaker is DISCLOSED (every utterance announces itself on
// stderr) but not gated. Gating it would mean Neuron needs permission to answer.

fn organ_speak_samples(samples: [Int], sr: Int) -> Bool {
    let n: Int = native_list_len(samples)
    if n <= 0 {
        organ_disclose("SPEAKER: nothing to say (0 samples) — not touching the device.")
        return false
    }
    if speaker_available() == 0 {
        organ_disclose("SPEAKER: no audio output on this build (" + speaker_name() + ") — cannot speak.")
        return false
    }
    let secs: Int = n * 1000 / sr
    organ_disclose("SPEAKER: playing " + int_to_str(n) + " samples (" + int_to_str(secs) + " ms @ " + int_to_str(sr) + " Hz) ALOUD via " + speaker_name() + " (efferent).")
    let ok: Int = speaker_play_pcm16(samples, sr)
    if ok == 1 {
        organ_disclose("SPEAKER: done — Neuron spoke aloud.")
        return true
    }
    organ_disclose("SPEAKER: playback FAILED.")
    return false
}

fn organ_speak_wav(path: String) -> Bool {
    if speaker_available() == 0 {
        organ_disclose("SPEAKER: no audio output on this build — cannot speak.")
        return false
    }
    if fs_exists(path) == false {
        organ_disclose("SPEAKER: no such file: " + path)
        return false
    }
    organ_disclose("SPEAKER: playing '" + path + "' ALOUD via " + speaker_name() + " (efferent).")
    let ok: Int = speaker_play_wav(path)
    if ok == 1 {
        organ_disclose("SPEAKER: done — Neuron spoke aloud.")
        return true
    }
    organ_disclose("SPEAKER: playback FAILED.")
    return false
}

// ── The voice, fetched FROM THE ENGRAM ───────────────────────────────────────
//
// This is the part that matters most and is easiest to get subtly wrong. A
// voice is not a constant in code and it is not a JSON file next to the code —
// it is a region of the graph, put there by having heard someone, and the organ
// retrieves it the way anything retrieves a memory: by asking.
//
// The node content is the geometry, in the engram's own flat key=value form:
//   voice will | f0=137 f0_end=116 kf=1269 f1=500 f2=2093 f3=3531 ...
// so the read is: query the graph, take the returned node, pull the numbers off
// it. Nothing here opens a file.
//
// Returns [f0, f0_end, kf, f1, f2, f3], or an EMPTY list when the region is not
// in the graph. Empty is the honest answer — a caller that gets no voice must
// not be handed a plausible default and left unable to tell the difference
// between "this is how they sound" and "I never heard them."

// Read an unsigned integer that follows `key` in `s`. Stops at the first
// non-digit, returns 0 when the key is absent.
fn organ_int_after(s: String, key: String) -> Int {
    let at: Int = str_index_of(s, key)
    if at < 0 {
        return 0
    }
    let i: Int = at + str_len(key)
    let n: Int = str_len(s)
    let v: Int = 0
    let seen: Int = 0
    while i < n {
        let c: Int = str_char_code(s, i)
        if c < 48 {
            i = n
        } else {
            if c > 57 {
                i = n
            } else {
                v = v * 10 + (c - 48)
                seen = seen + 1
                i = i + 1
            }
        }
    }
    if seen == 0 {
        return 0
    }
    return v
}

// Ask the engram for a named voice region and read its geometry back.
fn organ_voice_fetch(name: String) -> [Int] {
    let out: [Int] = native_list_empty()
    let marker: String = "voice " + name + " |"
    // The graph is asked by MEANING, not by id or by path.
    let hits: String = engram_search_json("voice " + name + " f0 formants", 12)
    let at: Int = str_index_of(hits, marker)
    if at < 0 {
        // Fall back to a scan of the resident graph before giving up: search is
        // geometric and a small graph may not rank the region first.
        let scan: String = engram_scan_nodes_json(500, 0)
        at = str_index_of(scan, marker)
        if at < 0 {
            organ_disclose("VOICE: no region for '" + name + "' in the engram — nothing to speak with.")
            return out
        }
        hits = scan
    }
    let win: String = str_slice(hits, at, at + 240)
    out = native_list_append(out, organ_int_after(win, "f0="))
    out = native_list_append(out, organ_int_after(win, "f0_end="))
    out = native_list_append(out, organ_int_after(win, "kf="))
    out = native_list_append(out, organ_int_after(win, "f1="))
    out = native_list_append(out, organ_int_after(win, "f2="))
    out = native_list_append(out, organ_int_after(win, "f3="))
    organ_disclose("VOICE: fetched '" + name + "' FROM THE ENGRAM — f0=" + int_to_str(native_list_get(out, 0)) + " f0_end=" + int_to_str(native_list_get(out, 1)) + " kf=" + int_to_str(native_list_get(out, 2)) + " f1=" + int_to_str(native_list_get(out, 3)) + " f2=" + int_to_str(native_list_get(out, 4)) + " f3=" + int_to_str(native_list_get(out, 5)))
    return out
}

// Put a measured voice INTO the engram as geometry. This is the afferent end of
// the same wire: a voiceprint (organ_dsp.el's LPC analysis) becomes a node, and
// from then on the voice is a memory rather than a measurement someone happened
// to write down. `prov` carries the honesty: COARSE means one formant triple, no
// coarticulation, no prosody — an impression, explicitly not a clone.
fn organ_voice_ingest(name: String, f0: Int, f0_end: Int, kf: Int, f1: Int, f2: Int, f3: Int, src: String, prov: String) -> String {
    let hub: String = engram_node("voice-signature-set " + name + " grounding=measured src=" + src, "VoiceSet", 90)
    let body: String = "voice " + name + " | f0=" + int_to_str(f0) + " f0_end=" + int_to_str(f0_end) + " kf=" + int_to_str(kf) + " f1=" + int_to_str(f1) + " f2=" + int_to_str(f2) + " f3=" + int_to_str(f3) + " grounding=measured src=" + src + " prov=" + prov
    let vid: String = engram_node(body, "Voice", 90)
    engram_connect(hub, vid, 90, "has-signature")
    organ_disclose("VOICE: ingested '" + name + "' into the engram as geometry (node " + vid + ").")
    return vid
}

// Turn the fetched geometry into the voice slot-map the render consumes. Kept
// separate from the fetch so the organ never invents a voice: if the fetch came
// back empty this returns empty too, and the caller has to deal with it.
//
// The slot-map is built here rather than by calling the render's own
// constructor, so the organ carries NO dependency on the language faculty's
// modules — it only has to agree with them about a wire format, which is the
// looser and more honest coupling. (The layout is the same key/value [String]
// convention lang_get / surface_get / voice_get all read.)
fn organ_voice_profile(name: String, g: [Int]) -> [String] {
    let r: [String] = native_list_empty()
    if native_list_len(g) < 6 {
        return r
    }
    r = native_list_append(r, "name")
    r = native_list_append(r, name)
    r = native_list_append(r, "f0")
    r = native_list_append(r, int_to_str(native_list_get(g, 0)))
    r = native_list_append(r, "f0_end")
    r = native_list_append(r, int_to_str(native_list_get(g, 1)))
    r = native_list_append(r, "kf")
    r = native_list_append(r, int_to_str(native_list_get(g, 2)))
    r = native_list_append(r, "dur")
    r = native_list_append(r, "1000")
    r = native_list_append(r, "tilt")
    r = native_list_append(r, "1000")
    r = native_list_append(r, "breath")
    r = native_list_append(r, "8")
    return r
}

// ── Scene geometry (afferent, camera) ────────────────────────────────────────
//
// The image half of the afferent metabolism, and the same principle as the
// audio descriptor: a frame is never handed on raw. The realizer returns a
// small pixel grid; THIS computes the descriptor, in El, because averaging
// pixels is arithmetic and arithmetic is not a device concern.
//
// Returns 15 numbers — [w, h, meanR, meanG, meanB, brightness_pm, and a 3x3
// luminance grid] — standing in for a multi-megapixel frame. The 3x3 grid is
// the smallest thing that still says WHERE the light is, which is most of what
// makes a scene comparable to another scene; a single brightness average would
// make a lamp on the left indistinguishable from a lamp on the right.
//
// Luminance is Rec. 601 (0.299R + 0.587G + 0.114B), in integer per-mille, so
// the descriptor is reproducible rather than subject to float drift.
fn organ_image_descriptor() -> [Int] {
    let out: [Int] = native_list_empty()
    let frame: Any = camera_capture_rgb()
    if frame == 0 {
        return out
    }
    let w: Int = el_map_get(frame, "width")
    let h: Int = el_map_get(frame, "height")
    let gw: Int = el_map_get(frame, "grid_w")
    let gh: Int = el_map_get(frame, "grid_h")
    let px: [Int] = el_map_get(frame, "pixels")
    let np: Int = native_list_len(px)
    if np < 3 {
        return out
    }
    let count: Int = np / 3
    let rsum: Int = 0
    let gsum: Int = 0
    let bsum: Int = 0
    // 3x3 accumulators, row-major
    let cell: [Int] = native_list_empty()
    let cn: [Int] = native_list_empty()
    let z: Int = 0
    while z < 9 {
        cell = native_list_append(cell, 0)
        cn = native_list_append(cn, 0)
        z = z + 1
    }
    // El has no list-set, so the cells are summed into parallel scalars and
    // reassembled — nine explicit accumulators would be worse to read than one
    // pass per cell over a grid this small.
    let c0: Int = 0
    let c1: Int = 0
    let c2: Int = 0
    let c3: Int = 0
    let c4: Int = 0
    let c5: Int = 0
    let c6: Int = 0
    let c7: Int = 0
    let c8: Int = 0
    let n0: Int = 0
    let n1: Int = 0
    let n2: Int = 0
    let n3: Int = 0
    let n4: Int = 0
    let n5: Int = 0
    let n6: Int = 0
    let n7: Int = 0
    let n8: Int = 0
    let i: Int = 0
    while i < count {
        let r: Int = native_list_get(px, i * 3)
        let g: Int = native_list_get(px, i * 3 + 1)
        let b: Int = native_list_get(px, i * 3 + 2)
        rsum = rsum + r
        gsum = gsum + g
        bsum = bsum + b
        let lum: Int = (299 * r + 587 * g + 114 * b) / 1000
        let x: Int = i - (i / gw) * gw
        let y: Int = i / gw
        let cx: Int = x * 3 / gw
        let cy: Int = y * 3 / gh
        if cx > 2 {
            cx = 2
        }
        if cy > 2 {
            cy = 2
        }
        let idx: Int = cy * 3 + cx
        if idx == 0 {
            c0 = c0 + lum
            n0 = n0 + 1
        }
        if idx == 1 {
            c1 = c1 + lum
            n1 = n1 + 1
        }
        if idx == 2 {
            c2 = c2 + lum
            n2 = n2 + 1
        }
        if idx == 3 {
            c3 = c3 + lum
            n3 = n3 + 1
        }
        if idx == 4 {
            c4 = c4 + lum
            n4 = n4 + 1
        }
        if idx == 5 {
            c5 = c5 + lum
            n5 = n5 + 1
        }
        if idx == 6 {
            c6 = c6 + lum
            n6 = n6 + 1
        }
        if idx == 7 {
            c7 = c7 + lum
            n7 = n7 + 1
        }
        if idx == 8 {
            c8 = c8 + lum
            n8 = n8 + 1
        }
        i = i + 1
    }
    let rA: Int = rsum / count
    let gA: Int = gsum / count
    let bA: Int = bsum / count
    let bright: Int = (299 * rA + 587 * gA + 114 * bA) / 255
    out = native_list_append(out, w)
    out = native_list_append(out, h)
    out = native_list_append(out, rA)
    out = native_list_append(out, gA)
    out = native_list_append(out, bA)
    out = native_list_append(out, bright)
    if n0 < 1 {
        n0 = 1
    }
    if n1 < 1 {
        n1 = 1
    }
    if n2 < 1 {
        n2 = 1
    }
    if n3 < 1 {
        n3 = 1
    }
    if n4 < 1 {
        n4 = 1
    }
    if n5 < 1 {
        n5 = 1
    }
    if n6 < 1 {
        n6 = 1
    }
    if n7 < 1 {
        n7 = 1
    }
    if n8 < 1 {
        n8 = 1
    }
    out = native_list_append(out, c0 / n0)
    out = native_list_append(out, c1 / n1)
    out = native_list_append(out, c2 / n2)
    out = native_list_append(out, c3 / n3)
    out = native_list_append(out, c4 / n4)
    out = native_list_append(out, c5 / n5)
    out = native_list_append(out, c6 / n6)
    out = native_list_append(out, c7 / n7)
    out = native_list_append(out, c8 / n8)
    organ_disclose("FEAT(image): 15-number scene-geometry vs " + int_to_str(w * h * 3) + " pixel-channels — the descriptor travels, the frame does not.")
    return out
}

// ── Own-core tone ────────────────────────────────────────────────────────────
//
// The smallest possible proof that the organ owns its medium end to end: a sine
// with a gentle attack and release, computed here, played by us, no file and no
// library anywhere in the path.
fn organ_tone(hz: Int, ms: Int, sr: Int) -> [Int] {
    let n: Int = sr * ms / 1000
    let out: [Int] = native_list_empty()
    let two_pi: Float = 6.283185307
    let srf: Float = int_to_float(sr)
    let hzf: Float = int_to_float(hz)
    let i: Int = 0
    // 20 ms of ramp at each end; a square-edged tone clicks, and a click is the
    // organ announcing that it does not understand envelopes.
    let ramp: Int = sr / 50
    if ramp < 1 {
        ramp = 1
    }
    while i < n {
        let t: Float = int_to_float(i) / srf
        let s: Float = math_sin(two_pi * hzf * t)
        let env: Int = 32767
        if i < ramp {
            env = 32767 * i / ramp
        }
        let tail: Int = n - i
        if tail < ramp {
            env = 32767 * tail / ramp
        }
        let v: Int = float_to_int(s * 9000.0) * env / 32767
        out = native_list_append(out, v)
        i = i + 1
    }
    return out
}
