<style>
span {
  font-size: xx-small;
}
</style>

<script>
import { uuid } from "utils"
import { getDirectoriesIn, getProgramPath, getFileMap } from './code-tools.js';
import Stream from 'src/client/reactive/stream.js';

import { isVariable as isVariablePath } from 'src/client/reactive/babel-plugin-active-expression-rewriting/utils.js';

function isVariable(path) {
  const parent = path.parentPath;
  return isVariablePath(path) &&
    !(parent.isMetaProperty()) &&
    !(parent.isForInStatement() && path.parentKey === 'left') &&
    !(parent.isAssignmentPattern() && path.parentKey === 'left') &&
    !(parent.isUpdateExpression()) &&
    !(parent.isFunctionExpression() && path.parentKey === 'id') &&
    !(parent.isImportDefaultSpecifier() && path.parentKey === 'local') &&
    !(parent.isCatchClause() && path.parentKey === 'param') &&
    !(parent.isContinueStatement() && path.parentKey === 'label') &&
    !parent.isObjectProperty() &&
    !parent.isClassDeclaration() &&
    !parent.isClassExpression() &&
    !parent.isClassMethod() &&
    !parent.isImportSpecifier() &&
    !parent.isObjectMethod() &&
    !(parent.isVariableDeclarator() && path.parentKey === 'id') &&
    !parent.isFunctionDeclaration() &&
    !(parent.isArrowFunctionExpression() && path.parentKey === 'params') &&
    !(parent.isExportSpecifier() && path.parentKey === 'exported') &&
    !(parent.isFunctionExpression() && path.parentKey === 'params') &&
    !parent.isRestElement() &&
    (!parent.isAssignmentExpression() || !(path.parentKey === 'left'));
}

async function getFilesIn(lookupDirectory) {
  const stats = await lively.files.stats(lookupDirectory);
  const files = stats.contents
    .filter(c => c.type === 'file')
    .map(c => lookupDirectory + c.name);
  return files;
}

const $allFiles = Stream.from(async function *() {
  const files = await getFilesIn('gs:programs/local/');
  yield* files.yieldAll()
});

const $programFiles = $allFiles.transform(async function *(input) {
  for await (let file of input) {
    if (file.endsWith('-program.js')) {
      yield file;
    }
  }
});

const $fileMap = $programFiles.transform(async function* $getFileMap (startingFiles, onFile = (info, filePath) => {}) {
  var fileMap = new Map();

  async function* addToFileMap(filePath) {
    if (fileMap.has(filePath)) { return; }
    const info = {};
    fileMap.set(filePath, info);
    if (!(await lively.files.exists(filePath))) {
      fileMap.delete(filePath);
      return;
    }

    const text = await filePath.fetchText();
    info.text = text;
    const program = getProgramPath(text);
    info.program = program;
    onFile(info, filePath);
    yield [filePath, info]

    const importedFiles = new Set();
    program.traverse({
      ImportDeclaration(path) {
        const importedFile = System.normalizeSync(path.node.source.value, filePath);
        importedFiles.add(importedFile);
      }
    });

    for (let importedFile of importedFiles) {
      yield* addToFileMap(importedFile);
    }
  }

  for await (let file of startingFiles) {
    yield* addToFileMap(file);
  }

  return fileMap;
});

let numberOfFiles = 0
const fileNumberLabel = <span style='color: orange'>{numberOfFiles} files</span>;
$fileMap.on('data', () => {
  numberOfFiles++
  fileNumberLabel.innerHTML = numberOfFiles + ' files'
});
$fileMap.on('end', () => {
  fileNumberLabel.style.color = null;
});
fileNumberLabel;
</script>

<script>

</script>
<script>
var editor = <lively-code-mirror style="border: 1px solid gray; width:500px;height:100px"></lively-code-mirror>;
async function getEditor() {
  const e = await editor;
  await e.editorLoaded();
  return e;
}
</script>

<script>
const identifierList = <table></table>;

function updateIdentifierList([file, info]) {
  identifierList.innerHTML = '';

  const vars = new Map();
  const others = new Map();
  info.program.traverse({
    Identifier(path) {
      (isVariable(path) ? vars : others).getOrCreate(path.node.name, () => new Set()).add(path);
    }
  });
  
  function tableDataFromMap(map) {
    const list = [];
    const entries = Array.from(map.entries())
    for (let [name, paths] of entries.sortBy('first')) {
      const pathBlocks = Array.from(paths).map(path => <span style='display: inline-block; margin: 2px; width: 10px; height: 10px; border: 2px solid gray; background-color: lightgray' mouseenter={function() {
        getEditor().then(editor => {
          editor.value = info.text;
          const location = path.node.loc;
          editor.editor.scrollIntoView({
            line: location.start.line - 1,
            ch: location.start.column
          }, 50);

          editor.editor.setSelection({
            line: location.start.line - 1,
            ch: location.start.column
          }, {
            line: location.end.line - 1,
            ch: location.end.column
          }, { scroll: false });
        })

      }}></span>);
      list.push(<tr><td>{name}</td><td>{paths.size}</td><td>{...pathBlocks}</td></tr>);
    }
    return list;
  }
  
  identifierList.append(...tableDataFromMap(vars));
  identifierList.append(<tr style='color: red;'><td colspan="3">------------ ------------ ------------</td></tr>);
  identifierList.append(...tableDataFromMap(others));
}

function selectionForList(optionsGenerator, callback) {
  const urlElement = <select style="width:500px"></select>;

  const m = new Map();
  (async () => {
    for await (let option of optionsGenerator) {
      const id = uuid();
      const label = option.first.replace(/.*\//gm, '').replace('-program.js', '');
      m.set(id, option);
      urlElement.append(<option value={id}>{label}</option>);
    }
  })();

  urlElement.addEventListener("change", () => callback(m.get(urlElement.value)));

  return urlElement;
}

selectionForList($fileMap.asAsyncGenerator(), updateIdentifierList);
</script>

#### Identifiers
<script>
editor
</script>

<script>
$fileMap.on('data', updateIdentifierList.once());

identifierList
</script>

#### Node Types by Number
<script>
const table = <table style='color: orange'></table>

const nodeTypeCount = new Map();

function updateNodeTypeCountTable() {
  const tableRows = [];
  const entries = Array.from(nodeTypeCount.entries())
  for (let [path, set] of entries.sortBy(([, set]) => set.size).reverse()) {
    tableRows.push(<tr><td>{path}</td> <td>{set.size}</td></tr>)
  }
  table.innerHTML = ''
  table.append(...tableRows)
}

$fileMap.on('data', ([key, info]) => {
  info.program.traverse({
    enter(path) {
      var set = nodeTypeCount.getOrCreate(path.node.type, () => new Set());
      set.add(path);
    }
  });
  updateNodeTypeCountTable();
});
$fileMap.on('end', () => table.style.color = null);

table
</script>
