<commit_before>defmodule Rondo.Element do
  defstruct [:key, :type, :props, :children]

  defmacro __using__(_) do
    quote do
      import unquote(__MODULE__), only: [el: 1, el: 2, el: 3]
    end
  end

  def el(type, props \\ %{}, children \\ []) when is_map(props) or is_nil(props) do
    props = props || %{}
    %__MODULE__{key: Map.get(props, :key, nil), type: type, props: props, children: children}
  end

  def state(element = %{props: props, children: children}, context) do
    props = Map.put(props, :children, children)
    call(element, :state, [props, context], props)
  end

  def context(element, state) do
    call(element, :context, [state], %{})
  end

  def render(element, state) do
    call(element, :render, [state], nil)
  end

  defp call(%{type: type}, fun, args, default) when is_atom(type) do
    case function_exported?(type, fun, length(args)) do
      true ->
        apply(type, fun, args)
      false ->
        default
    end
  end
  defp call(_, _, _, default) do
    default
  end
end

defimpl Rondo.Diffable, for: Rondo.Element do
  def diff(current, current, _path) do
    []
  end
  def diff(curr = %{type: t, props: p}, prev = %{type: t, props: p}, path) do
    diff_children(curr, prev, path)
  end
  def diff(curr = %{type: t, props: curr_p}, prev = %{type: t, props: prev_p}, path) do
    p_ops = Rondo.Diff.diff(curr_p, prev_p, ["props" | path])
    c_ops = diff_children(curr, prev, path)
    Stream.concat(p_ops, c_ops)
  end
  def diff(curr, _, path) do
    [Rondo.Operation.replace(path, curr)]
  end

  defp diff_children(%{children: current}, %{children: current}, _path) do
    []
  end
  defp diff_children(%{children: current}, %{children: prev}, path) do
    current = children_to_map(current)
    prev = children_to_map(prev)
    Rondo.Diff.diff(current, prev, ["children" | path])
  end

  defp children_to_map(children) do
    children
    |> Enum.reduce({%{}, 0}, fn
      (el = %Rondo.Element{key: key}, {map, idx}) when not is_nil(key) ->
        {Map.put(map, key, el), idx}
      (other, {map, idx}) ->
        {Map.put(map, idx, other), idx + 1}
    end)
    |> elem(0)
  end
end

defimpl Rondo.Traverser, for: Rondo.Element do
  def traverse(node, path, acc, prewalk, postwalk) do<commit_msg>disable the key on element diffs for now<commit_after>defmodule Rondo.Element do
  defstruct [:key, :type, :props, :children]

  defmacro __using__(_) do
    quote do
      import unquote(__MODULE__), only: [el: 1, el: 2, el: 3]
    end
  end

  def el(type, props \\ %{}, children \\ []) when is_map(props) or is_nil(props) do
    props = props || %{}
    %__MODULE__{key: Map.get(props, :key, nil), type: type, props: props, children: children}
  end

  def state(element = %{props: props, children: children}, context) do
    props = Map.put(props, :children, children)
    call(element, :state, [props, context], props)
  end

  def context(element, state) do
    call(element, :context, [state], %{})
  end

  def render(element, state) do
    call(element, :render, [state], nil)
  end

  defp call(%{type: type}, fun, args, default) when is_atom(type) do
    case function_exported?(type, fun, length(args)) do
      true ->
        apply(type, fun, args)
      false ->
        default
    end
  end
  defp call(_, _, _, default) do
    default
  end
end

defimpl Rondo.Diffable, for: Rondo.Element do
  def diff(current, current, _path) do
    []
  end
  def diff(curr = %{type: t, props: p}, prev = %{type: t, props: p}, path) do
    diff_children(curr, prev, path)
  end
  def diff(curr = %{type: t, props: curr_p}, prev = %{type: t, props: prev_p}, path) do
    p_ops = Rondo.Diff.diff(curr_p, prev_p, ["props" | path])
    c_ops = diff_children(curr, prev, path)
    Stream.concat(p_ops, c_ops)
  end
  def diff(curr, _, path) do
    [Rondo.Operation.replace(path, curr)]
  end

  defp diff_children(%{children: current}, %{children: current}, _path) do
    []
  end
  defp diff_children(%{children: current}, %{children: prev}, path) do
    current = children_to_map(current)
    prev = children_to_map(prev)
    Rondo.Diff.diff(current, prev, ["children" | path])
  end

  defp children_to_map(children) do
    children
    |> Enum.reduce({%{}, 0}, fn
      #TODO how do we represent ordering with a map?
      #(el = %Rondo.Element{key: key}, {map, idx}) when not is_nil(key) ->
      #  {Map.put(map, key, el), idx}
      (other, {map, idx}) ->
        {Map.put(map, idx, other), idx + 1}
    end)
    |> elem(0)
  end
end

defimpl Rondo.Traverser, for: Rondo.Element do
  def traverse(node, path, acc, prewalk, postwalk) do
{'fraction_non_alphanumeric': 0.1491304347826087, 'fraction_numerical': 0.0026086956521739132, 'mean_word_length': 3.2720519962859798, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7618700', 'n_tokens_mistral': 1664, 'n_tokens_neox': 1621, 'n_words': 489}