import * as React from 'react';

type BrowserProps = {
  context: any;
  connection: any;
  siteUrl: string;
  libraryNameOrId: string;
  folderPath: string;
  title: string;
};

type BrowserState = {
  rootItems: any[];
  childrenById: { [key: string]: any[] };
  expanded: { [key: string]: boolean };
  loadingFolders: { [key: string]: boolean };
  loading: boolean;
  error: string;
  driveId: string;
  resolvedPath: string;
};

function valueToText(value: any): string {
  if (value === null || value === undefined) return '';
  if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
    return String(value).trim();
  }
  if (Array.isArray(value)) return value.length ? valueToText(value[0]) : '';
  if (typeof value === 'object') {
    var keys = ['value', 'Value', 'text', 'Text', 'name', 'Name', 'label', 'Label'];
    for (var i = 0; i < keys.length; i += 1) {
      if (value[keys[i]] !== undefined) {
        var nested = valueToText(value[keys[i]]);
        if (nested) return nested;
      }
    }
  }
  return '';
}

function normalizePath(value: any): string {
  var raw = valueToText(value).replace(/\\/g, '/');
  var parts = raw.split('/').map(function (part) { return part.trim(); }).filter(Boolean);
  for (var i = 0; i < parts.length; i += 1) {
    if (parts[i] === '.' || parts[i] === '..') throw new Error('Folder path cannot contain . or .. segments.');
  }
  return parts.join('/');
}

function encodePath(path: string): string {
  if (!path) return '';
  return path.split('/').map(function (part) { return encodeURIComponent(part); }).join('/');
}

function safeError(error: any): string {
  var message = error && error.message ? error.message : String(error || 'Unknown error');
  return message.slice(0, 500);
}

function formatSize(value: any): string {
  var size = Number(value || 0);
  if (!isFinite(size) || size <= 0) return '—';
  var units = ['B', 'KB', 'MB', 'GB', 'TB'];
  var index = 0;
  while (size >= 1024 && index < units.length - 1) {
    size = size / 1024;
    index += 1;
  }
  return (index === 0 ? String(Math.round(size)) : size.toFixed(size >= 10 ? 1 : 2)) + ' ' + units[index];
}

function formatDate(value: any): string {
  if (!value) return '—';
  var date = new Date(value);
  if (isNaN(date.getTime())) return '—';
  return date.toLocaleString();
}

function sortItems(items: any[]): any[] {
  return (items || []).slice().sort(function (left, right) {
    var leftFolder = left && left.folder ? 0 : 1;
    var rightFolder = right && right.folder ? 0 : 1;
    if (leftFolder !== rightFolder) return leftFolder - rightFolder;
    return String(left.name || '').localeCompare(String(right.name || ''), undefined, { sensitivity: 'base' });
  });
}

class SharePointDocumentBrowser extends React.Component<BrowserProps, BrowserState> {
  constructor(props: BrowserProps) {
    super(props);
    this.state = {
      rootItems: [],
      childrenById: {},
      expanded: {},
      loadingFolders: {},
      loading: false,
      error: '',
      driveId: '',
      resolvedPath: ''
    };
    this.reload = this.reload.bind(this);
  }

  componentDidMount() {
    this.reload();
  }

  componentWillReceiveProps(nextProps: BrowserProps) {
    var currentKey = [this.props.siteUrl, this.props.libraryNameOrId, this.props.folderPath, this.props.connection].join('|');
    var nextKey = [nextProps.siteUrl, nextProps.libraryNameOrId, nextProps.folderPath, nextProps.connection].join('|');
    if (currentKey !== nextKey) {
      this.setState({ rootItems: [], childrenById: {}, expanded: {}, driveId: '', error: '' }, function () {
        this.reload(nextProps);
      });
    }
  }

  graphFetch(url: string, props?: BrowserProps): Promise<any> {
    var activeProps = props || this.props;
    var modules = activeProps.context && activeProps.context.modules;
    if (!modules || typeof modules.fetch !== 'function') {
      return Promise.reject(new Error('The Yeeflow runtime did not provide context.modules.fetch.'));
    }
    if (!activeProps.connection) {
      return Promise.reject(new Error('Select a Microsoft Graph Connection for this Custom Code control.'));
    }
    var headers: any = { Accept: 'application/json' };
    var candidate: any = activeProps.connection || {};
    if (candidate.Authorization) headers.Authorization = candidate.Authorization;
    else if (candidate.authorization) headers.Authorization = candidate.authorization;
    else if (candidate.accessToken) headers.Authorization = 'Bearer ' + candidate.accessToken;
    else if (candidate.access_token) headers.Authorization = 'Bearer ' + candidate.access_token;
    else if (candidate.token) headers.Authorization = 'Bearer ' + candidate.token;
    else if (candidate.headers && (candidate.headers.Authorization || candidate.headers.authorization)) {
      headers.Authorization = candidate.headers.Authorization || candidate.headers.authorization;
    }
    return modules.fetch(url, {
      method: 'GET',
      headers: headers,
      connection: activeProps.connection
    }).then(function (response: any) {
      return response.json().catch(function () { return null; }).then(function (data: any) {
        if (!response.ok) {
          var graphMessage = data && data.error && data.error.message ? data.error.message : 'HTTP ' + response.status;
          throw new Error('Microsoft Graph request failed: ' + graphMessage);
        }
        return data || {};
      });
    });
  }

  graphCollection(url: string, props?: BrowserProps): Promise<any[]> {
    var self = this;
    var rows: any[] = [];
    function next(pageUrl: string): Promise<any[]> {
      return self.graphFetch(pageUrl, props).then(function (data: any) {
        rows = rows.concat(Array.isArray(data.value) ? data.value : []);
        var nextLink = data['@odata.nextLink'];
        return nextLink ? next(nextLink) : rows;
      });
    }
    return next(url);
  }

  parseSite(siteUrl: string): { hostname: string; sitePath: string } {
    var parser = document.createElement('a');
    parser.href = siteUrl;
    if (parser.protocol !== 'https:' || !parser.hostname) throw new Error('Site URL must be a valid HTTPS SharePoint site URL.');
    var path = parser.pathname || '/';
    path = path === '/' ? '/' : path.replace(/\/+$/, '');
    return { hostname: parser.hostname, sitePath: path };
  }

  resolveDrive(props: BrowserProps): Promise<string> {
    var self = this;
    var site = this.parseSite(props.siteUrl);
    var siteEndpoint = 'https://graph.microsoft.com/v1.0/sites/' + site.hostname + ':' + site.sitePath;
    return this.graphFetch(siteEndpoint + '?$select=id,webUrl', props).then(function (siteData: any) {
      if (!siteData.id) throw new Error('Microsoft Graph did not return the SharePoint site ID.');
      var requested = props.libraryNameOrId.toLowerCase();
      return self.graphCollection('https://graph.microsoft.com/v1.0/sites/' + encodeURIComponent(siteData.id) + '/drives?$select=id,name,webUrl,driveType', props)
        .then(function (drives: any[]) {
          var drive = drives.filter(function (item: any) {
            return String(item.id || '').toLowerCase() === requested || String(item.name || '').toLowerCase() === requested;
          })[0];
          if (!drive || !drive.id) throw new Error('Document library was not found by name or ID.');
          return String(drive.id);
        });
    });
  }

  loadChildren(driveId: string, folderId?: string, path?: string, props?: BrowserProps): Promise<any[]> {
    var select = '?$select=id,name,folder,file,webUrl,size,lastModifiedDateTime,parentReference';
    var endpoint: string;
    if (folderId) {
      endpoint = 'https://graph.microsoft.com/v1.0/drives/' + encodeURIComponent(driveId) + '/items/' + encodeURIComponent(folderId) + '/children' + select;
    } else if (path) {
      endpoint = 'https://graph.microsoft.com/v1.0/drives/' + encodeURIComponent(driveId) + '/root:/' + encodePath(path) + ':/children' + select;
    } else {
      endpoint = 'https://graph.microsoft.com/v1.0/drives/' + encodeURIComponent(driveId) + '/root/children' + select;
    }
    return this.graphCollection(endpoint, props).then(sortItems);
  }

  reload(overrideProps?: BrowserProps) {
    var self = this;
    var props = overrideProps || this.props;
    if (!props.siteUrl || !props.libraryNameOrId) {
      this.setState({ loading: false, error: 'Configure Site URL and Document Library Name or ID.', rootItems: [] });
      return;
    }
    var path = '';
    try {
      path = normalizePath(props.folderPath);
    } catch (error) {
      this.setState({ loading: false, error: safeError(error), rootItems: [] });
      return;
    }
    this.setState({ loading: true, error: '', rootItems: [], childrenById: {}, expanded: {}, resolvedPath: path });
    this.resolveDrive(props)
      .then(function (driveId: string) {
        return self.loadChildren(driveId, '', path, props).then(function (items: any[]) {
          self.setState({ driveId: driveId, rootItems: items, loading: false });
        });
      })
      .catch(function (error: any) {
        self.setState({ loading: false, error: safeError(error), driveId: '', rootItems: [] });
      });
  }

  toggleFolder(item: any) {
    var self = this;
    var id = String(item.id || '');
    if (!id || !item.folder) return;
    if (this.state.expanded[id]) {
      var collapsed = Object.assign({}, this.state.expanded);
      collapsed[id] = false;
      this.setState({ expanded: collapsed });
      return;
    }
    var expanded = Object.assign({}, this.state.expanded);
    expanded[id] = true;
    if (this.state.childrenById[id]) {
      this.setState({ expanded: expanded });
      return;
    }
    var loadingFolders = Object.assign({}, this.state.loadingFolders);
    loadingFolders[id] = true;
    this.setState({ expanded: expanded, loadingFolders: loadingFolders });
    this.loadChildren(this.state.driveId, id)
      .then(function (items: any[]) {
        var childrenById = Object.assign({}, self.state.childrenById);
        var nextLoading = Object.assign({}, self.state.loadingFolders);
        childrenById[id] = items;
        nextLoading[id] = false;
        self.setState({ childrenById: childrenById, loadingFolders: nextLoading });
      })
      .catch(function (error: any) {
        var nextExpanded = Object.assign({}, self.state.expanded);
        var nextLoading = Object.assign({}, self.state.loadingFolders);
        nextExpanded[id] = false;
        nextLoading[id] = false;
        self.setState({ expanded: nextExpanded, loadingFolders: nextLoading, error: safeError(error) });
      });
  }

  renderRows(items: any[], depth: number): any[] {
    var self = this;
    var rows: any[] = [];
    (items || []).forEach(function (item: any) {
      var id = String(item.id || item.name || Math.random());
      var isFolder = !!item.folder;
      var isExpanded = !!self.state.expanded[id];
      var isLoading = !!self.state.loadingFolders[id];
      rows.push(
        <tr key={id} className="spdb-row">
          <td className="spdb-name-cell">
            <div className="spdb-name-wrap" style={{ paddingLeft: (depth * 24) + 'px' }}>
              {isFolder ? (
                <button type="button" className="spdb-chevron" aria-label={isExpanded ? 'Collapse folder' : 'Expand folder'} onClick={function () { self.toggleFolder(item); }}>
                  {isLoading ? '…' : (isExpanded ? '⌄' : '›')}
                </button>
              ) : <span className="spdb-chevron-spacer" />}
              {isFolder ? <span className="spdb-icon spdb-folder-icon" aria-hidden="true" /> : <span className="spdb-icon spdb-file">▤</span>}
              {isFolder ? (
                <button type="button" className="spdb-name-button" onClick={function () { self.toggleFolder(item); }}>{item.name || 'Unnamed folder'}</button>
              ) : (
                <a className="spdb-file-link" href={item.webUrl || '#'} target="_blank" rel="noopener noreferrer">{item.name || 'Unnamed file'}</a>
              )}
            </div>
          </td>
          <td className="spdb-type-cell">{isFolder ? 'Folder' : ((item.file && item.file.mimeType) || 'File')}</td>
          <td className="spdb-date-cell">{formatDate(item.lastModifiedDateTime)}</td>
          <td className="spdb-size-cell">{isFolder ? '—' : formatSize(item.size)}</td>
          <td className="spdb-open-cell">{item.webUrl ? <a href={item.webUrl} target="_blank" rel="noopener noreferrer">Open</a> : '—'}</td>
        </tr>
      );
      if (isFolder && isExpanded) {
        var children = self.state.childrenById[id];
        if (children && children.length) rows = rows.concat(self.renderRows(children, depth + 1));
        if (children && !children.length && !isLoading) {
          rows.push(<tr key={id + '-empty'} className="spdb-child-empty"><td colSpan={5}><div style={{ paddingLeft: ((depth + 1) * 24 + 44) + 'px' }}>This folder is empty.</div></td></tr>);
        }
      }
    });
    return rows;
  }

  render() {
    var styles = [
      '.spdb{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Arial,sans-serif;border:1px solid #e5e9f0;border-radius:8px;background:#fff;color:#1f2937;overflow:hidden}',
      '.spdb-toolbar{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 16px;border-bottom:1px solid #e8ecf2;background:#fafbfd}',
      '.spdb-title{font-size:16px;font-weight:600;color:#172033}.spdb-path{margin-top:3px;font-size:12px;color:#667085}',
      '.spdb-refresh{border:1px solid #cfd7e3;border-radius:5px;background:#fff;color:#146ff6;padding:6px 12px;cursor:pointer}.spdb-refresh:hover{background:#f3f7ff}.spdb-refresh:disabled{color:#98a2b3;cursor:default}',
      '.spdb-alert{margin:14px 16px;padding:10px 12px;border:1px solid #f2c7c7;border-radius:6px;background:#fff7f7;color:#9f2d2d}',
      '.spdb-loading,.spdb-empty{padding:34px 20px;text-align:center;color:#667085}',
      '.spdb-table-wrap{width:100%;overflow:auto}.spdb-table{width:100%;border-collapse:collapse;table-layout:fixed}',
      '.spdb-table th{padding:10px 12px;border-bottom:1px solid #e8ecf2;background:#fff;color:#667085;font-size:12px;font-weight:600;text-align:left}',
      '.spdb-table td{padding:9px 12px;border-bottom:1px solid #edf0f4;font-size:13px;vertical-align:middle}.spdb-row:hover{background:#f7f9fc}',
      '.spdb-name-cell{width:48%}.spdb-type-cell{width:18%;color:#667085;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.spdb-date-cell{width:17%;color:#667085}.spdb-size-cell{width:9%;color:#667085;text-align:right}.spdb-open-cell{width:8%;text-align:right}',
      '.spdb-name-wrap{display:flex;align-items:center;min-width:240px}.spdb-chevron{width:24px;height:24px;border:0;background:transparent;color:#526071;cursor:pointer;font-size:20px;line-height:20px;padding:0}.spdb-chevron-spacer{display:inline-block;width:24px}',
      '.spdb-icon{display:inline-flex;width:20px;margin-right:7px;justify-content:center;flex:0 0 20px}.spdb-file{color:#146ff6;font-size:14px}',
      '.spdb-folder-icon{position:relative;width:16px;height:11px;flex:0 0 16px;margin-left:2px;margin-right:9px;margin-top:2px;background:#f4b41b;border:1px solid #cf9008;border-radius:2px;box-shadow:inset 0 1px 0 rgba(255,255,255,.35)}',
      '.spdb-folder-icon:before{content:"";position:absolute;left:1px;top:-4px;width:7px;height:4px;background:#f4b41b;border:1px solid #cf9008;border-bottom:0;border-radius:2px 2px 0 0}',
      '.spdb-name-button{border:0;background:transparent;padding:2px 0;color:#172033;cursor:pointer;text-align:left}.spdb-name-button:hover,.spdb-file-link:hover{text-decoration:underline}.spdb-file-link,.spdb-open-cell a{color:#146ff6;text-decoration:none}',
      '.spdb-child-empty td{padding:8px 12px;color:#98a2b3;font-size:12px;background:#fafbfd}',
      '@media(max-width:760px){.spdb-type-cell,.spdb-date-cell,.spdb-size-cell{display:none}.spdb-name-cell{width:78%}.spdb-open-cell{width:22%}.spdb-toolbar{align-items:flex-start}.spdb-title{font-size:15px}}'
    ].join('');
    var pathLabel = this.state.resolvedPath ? this.state.resolvedPath : 'Library root';
    return (
      <div className="spdb">
        <style>{styles}</style>
        <div className="spdb-toolbar">
          <div><div className="spdb-title">{this.props.title || 'SharePoint documents'}</div><div className="spdb-path">{pathLabel}</div></div>
          <button type="button" className="spdb-refresh" disabled={this.state.loading} onClick={function () { this.reload(); }.bind(this)}>{this.state.loading ? 'Loading…' : 'Refresh'}</button>
        </div>
        {this.state.error ? <div className="spdb-alert">{this.state.error}</div> : null}
        {this.state.loading ? <div className="spdb-loading">Loading SharePoint documents…</div> : null}
        {!this.state.loading && !this.state.error && !this.state.rootItems.length ? <div className="spdb-empty">No folders or files were found in this location.</div> : null}
        {!this.state.loading && this.state.rootItems.length ? (
          <div className="spdb-table-wrap"><table className="spdb-table"><thead><tr><th>Name</th><th className="spdb-type-cell">Type</th><th className="spdb-date-cell">Modified</th><th className="spdb-size-cell">Size</th><th className="spdb-open-cell"></th></tr></thead><tbody>{this.renderRows(this.state.rootItems, 0)}</tbody></table></div>
        ) : null}
      </div>
    );
  }
}

export class CodeInApplication implements CodeInComp {
  description() {
    return 'Displays a SharePoint document library folder as a OneDrive-style expandable file browser.';
  }

  requiredFields() {
    return [];
  }

  inputParameters(): InputParameter[] {
    return [
      { id: 'siteUrl', name: 'SharePoint Site URL', type: 'variable', description: 'HTTPS URL of the SharePoint team site.' },
      { id: 'libraryNameOrId', name: 'Document Library Name or ID', type: 'variable', description: 'Document library display name or Microsoft Graph drive ID.' },
      { id: 'folderPath', name: 'Folder Path', type: 'variable', description: 'Optional path relative to the library root. Leave empty to show the root.' },
      { id: 'title', name: 'Title', type: 'string', description: 'Optional heading shown above the file browser.' }
    ];
  }

  connections() {
    return [
      { id: 'sharePointConnection', desc: 'Microsoft Graph / SharePoint OAuth HTTP Connection' }
    ];
  }

  resolveConnection(context: any): any {
    if (context && context.connections && context.connections.sharePointConnection) return context.connections.sharePointConnection;
    if (context && typeof context.getConnection === 'function') {
      try { return context.getConnection('sharePointConnection'); } catch (error) { return null; }
    }
    return null;
  }

  render(context: CodeInContext, fieldsValues: any, readonly: boolean) {
    var params: any = context && context.params ? context.params : {};
    return <SharePointDocumentBrowser
      context={context}
      connection={this.resolveConnection(context)}
      siteUrl={valueToText(params.siteUrl)}
      libraryNameOrId={valueToText(params.libraryNameOrId)}
      folderPath={valueToText(params.folderPath)}
      title={valueToText(params.title) || 'SharePoint documents'}
    />;
  }
}
