fn projected_docs_record() -> Value {
    let mut record = Record::default();
    record.insert("title".to_string(), Value::String("Authoring Rules".into()));
    record.insert(
        "summary".to_string(),
        Value::String("Rules for editing workflow JSON drafts.".into()),
    );
    record.insert(
        "body".to_string(),
        Value::Projected(ProjectedValue::custom(
            "docs.body",
            Arc::new(ProjectedText::new("body", "lazy markdown body")),
        )),
    );
    Value::Record(Arc::new(record))
}

fn snapshot_projected_record() -> Value {
    let mut projected = Record::default();
    projected.insert(
        "body".to_string(),
        Value::Projected(ProjectedValue::custom(
            "snap.projected.body",
            Arc::new(ProjectedText::new(
                "snapshot_body",
                "projected body stays lazy across snapshot markers",
            )),
        )),
    );

    let mut normal = Record::default();
    normal.insert("title".to_string(), Value::String("Snapshot Rules".into()));
    normal.insert(
        "tags".to_string(),
        Value::List(
            vec![
                Value::String("snapshot".into()),
                Value::String("projected".into()),
                Value::String("mixed".into()),
            ]
            .into(),
        ),
    );

    let mut nested = Record::default();
    nested.insert(
        "projected_title".to_string(),
        Value::Projected(ProjectedValue::custom(
            "snap.mixed.nested.projected_title",
            Arc::new(ProjectedText::new("nested_title", "Nested Projection")),
        )),
    );

    let mut mixed = Record::default();
    mixed.insert("count".to_string(), Value::Number(7.0));
    mixed.insert("nested".to_string(), Value::Record(Arc::new(nested)));

    let mut root = Record::default();
    root.insert("id".to_string(), Value::String("snapshot-mixed".into()));
    root.insert("normal".to_string(), Value::Record(Arc::new(normal)));
    root.insert("projected".to_string(), Value::Record(Arc::new(projected)));
    root.insert("mixed".to_string(), Value::Record(Arc::new(mixed)));
    Value::Record(Arc::new(root))
}

fn projected_operations_record() -> Value {
    let mut record = Record::default();
    record.insert(
        "items".to_string(),
        Value::Projected(ProjectedValue::custom(
            "proj.items",
            Arc::new(ProjectedList {
                name: "items",
                values: vec![
                    Value::String("alpha".into()),
                    Value::String("beta".into()),
                    Value::String("gamma".into()),
                    Value::String("delta".into()),
                ]
                .into(),
            }),
        )),
    );
    record.insert(
        "text".to_string(),
        Value::Projected(ProjectedValue::custom(
            "proj.text",
            Arc::new(ProjectedText::new("text", "alpha beta gamma delta")),
        )),
    );
    record.insert(
        "padded".to_string(),
        Value::Projected(ProjectedValue::custom(
            "proj.padded",
            Arc::new(ProjectedText::new("padded", "  alpha  ")),
        )),
    );
    record.insert(
        "json".to_string(),
        Value::Projected(ProjectedValue::custom(
            "proj.json",
            Arc::new(ProjectedText::new("json", "{\"ok\":true,\"count\":4}")),
        )),
    );
    record.insert(
        "number".to_string(),
        Value::Projected(ProjectedValue::scalar("proj.number", Value::Number(4.0))),
    );
    record.insert(
        "record".to_string(),
        Value::Projected(ProjectedValue::scalar("proj.record", {
            let mut inner = Record::default();
            inner.insert("topic".to_string(), Value::String("bench".into()));
            inner.insert("count".to_string(), Value::Number(4.0));
            Value::Record(Arc::new(inner))
        })),
    );
    Value::Record(Arc::new(record))
}

struct ProjectedList {
    name: &'static str,
    values: ListValue,
}

impl ProjectedList {
    fn history() -> Self {
        Self {
            name: "history",
            values: vec![
                history_item("user", "alpha request"),
                history_item("assistant", "beta response"),
                history_item("tool", "gamma observation"),
            ]
            .into(),
        }
    }
}

fn string_value(value: &Value) -> Result<compact_str::CompactString, ()> {
    match value {
        Value::String(value) => Ok(value.clone()),
        Value::Number(value) => Ok(value.to_string().into()),
        Value::Bool(value) => Ok(if *value { "true" } else { "false" }.into()),
        Value::Null => Ok("null".into()),
        _ => Err(()),
    }
}

fn clamp_slice_bounds(
    start: Option<isize>,
    end: Option<isize>,
    len: usize,
) -> Option<(usize, usize)> {
    let len = len as isize;
    let normalize = |bound: Option<isize>, default| {
        let bound = bound.unwrap_or(default);
        if bound < 0 { len + bound } else { bound }.clamp(0, len)
    };
    let start = normalize(start, 0);
    let end = normalize(end, len);
    (start < end).then_some((start as usize, end as usize))
}

fn slice_string(text: &str, start: Option<isize>, end: Option<isize>) -> String {
    let chars = text.chars().collect::<Vec<_>>();
    let Some((start, end)) = clamp_slice_bounds(start, end, chars.len()) else {
        return String::new();
    };
    chars[start..end].iter().collect()
}

impl ProjectedHostDescriptor for ProjectedList {
    fn type_name(&self) -> &str {
        "list"
    }

    fn read_one(
        &self,
        request: ProjectedReadRequest,
    ) -> ProjectedFuture<'_, ProjectedReadResponse> {
        Box::pin(async move {
            let ProjectedReadRequest::Index(index) = request else {
                return match request {
                    ProjectedReadRequest::Len => ProjectedReadResponse::Len(self.values.len()),
                    ProjectedReadRequest::Empty => {
                        ProjectedReadResponse::Bool(self.values.is_empty())
                    }
                    ProjectedReadRequest::Truthy => ProjectedReadResponse::Bool(true),
                    ProjectedReadRequest::Contains(needle) => ProjectedReadResponse::Bool(
                        self.values.iter().any(|value| value == &needle),
                    ),
                    ProjectedReadRequest::Join(sep) => {
                        let Ok(sep) = string_value(&sep) else {
                            return ProjectedReadResponse::Missing;
                        };
                        let mut joined = String::new();
                        for (index, value) in self.values.iter().enumerate() {
                            let Ok(value) = string_value(value) else {
                                return ProjectedReadResponse::Missing;
                            };
                            if index > 0 {
                                joined.push_str(sep.as_ref());
                            }
                            joined.push_str(value.as_ref());
                        }
                        ProjectedReadResponse::Value(Value::String(joined.into()))
                    }
                    ProjectedReadRequest::Slice { start, end } => {
                        let Some((start, end)) = clamp_slice_bounds(start, end, self.values.len())
                        else {
                            return ProjectedReadResponse::Value(Value::List(Vec::new().into()));
                        };
                        ProjectedReadResponse::Value(Value::List(
                            self.values[start..end].to_vec().into(),
                        ))
                    }
                    ProjectedReadRequest::Push(item) => {
                        let mut values = self.values.to_vec();
                        values.push(item);
                        ProjectedReadResponse::Value(Value::List(values.into()))
                    }
                    ProjectedReadRequest::Render => ProjectedReadResponse::Text(format!(
                        "<{}:{}>",
                        self.name,
                        self.values.len()
                    )),
                    ProjectedReadRequest::Materialize => {
                        ProjectedReadResponse::Value(Value::List(self.values.clone()))
                    }
                    _ => ProjectedReadResponse::Missing,
                };
            };
            let Some(index) = resolve_index(&index, self.values.len()) else {
                return ProjectedReadResponse::Missing;
            };
            self.values
                .get(index)
                .cloned()
                .map(ProjectedReadResponse::Value)
                .unwrap_or(ProjectedReadResponse::Missing)
        })
    }
}

fn history_item(role: &str, content: &str) -> Value {
    let mut record = Record::default();
    record.insert("role".to_string(), Value::String(role.to_string().into()));
    record.insert(
        "content".to_string(),
        Value::Projected(ProjectedValue::custom(
            format!("history.{role}.content"),
            Arc::new(ProjectedText::new("content", content)),
        )),
    );
    Value::Record(Arc::new(record))
}

struct ProjectedText {
    name: &'static str,
    text: Arc<str>,
}

impl ProjectedText {
    fn new(name: &'static str, text: impl Into<Arc<str>>) -> Self {
        Self {
            name,
            text: text.into(),
        }
    }
}

impl ProjectedHostDescriptor for ProjectedText {
    fn type_name(&self) -> &str {
        "string"
    }

    fn read_one(
        &self,
        request: ProjectedReadRequest,
    ) -> ProjectedFuture<'_, ProjectedReadResponse> {
        Box::pin(async move {
            match request {
                ProjectedReadRequest::Len => ProjectedReadResponse::Len(self.text.chars().count()),
                ProjectedReadRequest::Empty => ProjectedReadResponse::Bool(self.text.is_empty()),
                ProjectedReadRequest::Truthy => ProjectedReadResponse::Bool(!self.text.is_empty()),
                ProjectedReadRequest::Index(index) => {
                    let Some(index) = resolve_index(&index, self.text.chars().count()) else {
                        return ProjectedReadResponse::Missing;
                    };
                    self.text
                        .chars()
                        .nth(index)
                        .map(|ch| {
                            ProjectedReadResponse::Value(Value::String(ch.to_compact_string()))
                        })
                        .unwrap_or(ProjectedReadResponse::Missing)
                }
                ProjectedReadRequest::Find { needle, start } => {
                    let Value::String(needle) = needle else {
                        return ProjectedReadResponse::Missing;
                    };
                    ProjectedReadResponse::Value(match find_text(&self.text, &needle, start) {
                        Some(index) => Value::Number(index as f64),
                        None => Value::Null,
                    })
                }
                ProjectedReadRequest::GrepText(needle) => {
                    let Value::String(needle) = needle else {
                        return ProjectedReadResponse::Missing;
                    };
                    grep_text_records(&self.text, &needle)
                        .map(ProjectedReadResponse::Value)
                        .unwrap_or(ProjectedReadResponse::Missing)
                }
                ProjectedReadRequest::StartsWith(prefix) => {
                    let Value::String(prefix) = prefix else {
                        return ProjectedReadResponse::Missing;
                    };
                    ProjectedReadResponse::Bool(self.text.starts_with(&*prefix))
                }
                ProjectedReadRequest::EndsWith(suffix) => {
                    let Value::String(suffix) = suffix else {
                        return ProjectedReadResponse::Missing;
                    };
                    ProjectedReadResponse::Bool(self.text.ends_with(&*suffix))
                }
                ProjectedReadRequest::Split(needle) => {
                    let Value::String(needle) = needle else {
                        return ProjectedReadResponse::Missing;
                    };
                    ProjectedReadResponse::Value(Value::List(
                        self.text
                            .split(&*needle)
                            .map(|part| Value::String(part.into()))
                            .collect::<Vec<_>>()
                            .into(),
                    ))
                }
                ProjectedReadRequest::Trim => {
                    ProjectedReadResponse::Value(Value::String(self.text.trim().into()))
                }
                ProjectedReadRequest::Slice { start, end } => ProjectedReadResponse::Value(
                    Value::String(slice_string(&self.text, start, end).into()),
                ),
                ProjectedReadRequest::ToNumber => self
                    .text
                    .parse::<f64>()
                    .ok()
                    .map(Value::Number)
                    .map(ProjectedReadResponse::Value)
                    .unwrap_or(ProjectedReadResponse::Missing),
                ProjectedReadRequest::JsonParse => {
                    serde_json::from_str::<serde_json::Value>(&self.text)
                        .map(from_json)
                        .map(ProjectedReadResponse::Value)
                        .unwrap_or(ProjectedReadResponse::Missing)
                }
                ProjectedReadRequest::Render => ProjectedReadResponse::Text(format!(
                    "<{}:{} chars>",
                    self.name,
                    self.text.chars().count()
                )),
                ProjectedReadRequest::Materialize => {
                    ProjectedReadResponse::Value(Value::String(self.text.as_ref().into()))
                }
                _ => ProjectedReadResponse::Missing,
            }
        })
    }
}

fn grep_text_records(text: &str, needle: &str) -> Option<Value> {
    if needle.is_empty() {
        return None;
    }

    let needle_len = needle.chars().count();
    let needle_value = Value::String(needle.into());
    let mut matches = Vec::new();
    for (line_index, line) in text.lines().enumerate() {
        let Some(start) = find_text(line, needle, 0) else {
            continue;
        };
        let mut record = Record::with_capacity(5);
        record.insert_str("line", Value::Number((line_index + 1) as f64));
        record.insert_str("text", Value::String(line.into()));
        record.insert_str("match", needle_value.clone());
        record.insert_str("start", Value::Number(start as f64));
        record.insert_str("end", Value::Number((start + needle_len) as f64));
        matches.push(Value::Record(Arc::new(record)));
    }
    Some(Value::List(matches.into()))
}

fn find_text(text: &str, needle: &str, start: usize) -> Option<usize> {
    let start_byte = if start == 0 {
        0
    } else {
        byte_index_for_char(text, start)?
    };
    if needle.is_empty() {
        return Some(start);
    }
    let tail = &text[start_byte..];
    let match_byte = tail.find(needle)?;
    Some(start + tail[..match_byte].chars().count())
}

fn byte_index_for_char(text: &str, target: usize) -> Option<usize> {
    let mut char_count = 0;
    for (byte_index, _) in text.char_indices() {
        if char_count == target {
            return Some(byte_index);
        }
        char_count += 1;
    }
    (char_count == target).then_some(text.len())
}

fn resolve_index(index: &Value, len: usize) -> Option<usize> {
    let Value::Number(index) = index else {
        return None;
    };
    if !index.is_finite() || index.fract() != 0.0 {
        return None;
    }
    let len = len as isize;
    let index = *index as isize;
    let normalized = if index < 0 { len + index } else { index };
    (0..len)
        .contains(&normalized)
        .then_some(normalized as usize)
}

pub struct BenchHost;
