'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');

var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);

var _extends2 = require('babel-runtime/helpers/extends');

var _extends3 = _interopRequireDefault(_extends2);

var _config = require('@expo/config');

var ConfigUtils = _interopRequireWildcard(_config);

var _xdl = require('@expo/xdl');

var _graphqlTools = require('graphql-tools');

var _iterall = require('iterall');

var _mergeAsyncIterators = require('../asynciterators/mergeAsyncIterators');

var _mergeAsyncIterators2 = _interopRequireDefault(_mergeAsyncIterators);

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

// for prettier
const graphql = text => text;


const typeDefs = graphql`
  enum Platform {
    ANDROID
    IOS
  }

  enum HostType {
    lan
    localhost
    tunnel
  }

  type User {
    username: String
  }

  type Project {
    id: ID!
    # Absolute path of the project directory.
    projectDir: String!
    # The URL where the Expo manifest is being served.
    manifestUrl: String
    # Settings specific to this project, e.g. URL settings.
    settings: ProjectSettings!
    # Project configuration from app.json.
    config: ProjectConfig
    # Things that can send messages
    sources: [Source]
    # All messages from all sources
    messages: MessageConnection!
  }

  type ProjectSettings {
    hostType: HostType!
    dev: Boolean!
    minify: Boolean!
  }

  input ProjectSettingsInput {
    hostType: HostType
    dev: Boolean
    minify: Boolean
  }

  type ProjectConfig {
    name: String
    description: String
    slug: String
    githubUrl: String
  }

  input ProjectConfigInput {
    name: String
    description: String
    slug: String
    githubUrl: String
  }

  type UserSettings {
    id: ID!
    sendTo: String
  }

  enum SendMedium {
    email
    sms
  }

  type SendProjectResult {
    medium: SendMedium!
    url: String!
  }

  union OpenSimulatorResult = OpenSimulatorSuccess | OpenSimulatorError

  type OpenSimulatorSuccess {
    url: String!
  }

  type OpenSimulatorError {
    error: String!
  }

  type PublishProjectResult {
    url: String!
  }

  interface Source {
    id: ID!
    name: String!
    messages: MessageConnection!
  }

  type Issues implements Source {
    id: ID!
    name: String!
    messages: MessageConnection!
  }

  type Process implements Source {
    id: ID!
    name: String!
    messages: MessageConnection!
  }

  type Device implements Source {
    id: ID!
    name: String!
    messages: MessageConnection!
  }

  enum LogLevel {
    DEBUG
    INFO
    WARN
    ERROR
  }

  interface Message {
    id: ID!
    msg: String!
    time: String!
    source: Source!
    level: LogLevel!
  }

  type Issue implements Message {
    id: ID!
    msg: String!
    time: String!
    source: Issues!
    level: LogLevel!
  }

  type LogMessage implements Message {
    id: ID!
    msg: String!
    time: String!
    source: Process!
    level: LogLevel!
  }

  type DeviceMessage implements Message {
    id: ID!
    msg: String!
    time: String!
    source: Device!
    level: LogLevel!
    includesStack: Boolean
  }

  type MetroInitializeStarted implements Message {
    id: ID!
    msg: String!
    time: String!
    source: Process!
    level: LogLevel!
  }

  type BuildProgress implements Message {
    id: ID!
    msg: String!
    time: String!
    source: Process!
    level: LogLevel!
    progress: Int!
    duration: Int!
  }

  type BuildFinished implements Message {
    id: ID!
    msg: String!
    time: String!
    source: Process!
    level: LogLevel!
    duration: Int!
  }

  type BuildError implements Message {
    id: ID!
    msg: String!
    time: String!
    source: Process!
    level: LogLevel!
    error: String!
    duration: Int!
  }

  type TunnelReady implements Message {
    id: ID!
    msg: String!
    time: String!
    source: Process!
    level: LogLevel!
  }

  type PageInfo {
    lastReadCursor: String
    lastCursor: String
  }

  type MessageConnection {
    count: Int!
    unreadCount: Int!
    nodes: [Message!]
    pageInfo: PageInfo
  }

  enum MessagePayloadType {
    ADDED
    UPDATED
    DELETED
  }

  union MessageSubscriptionPayload = MessagePayload | SourceClearedPayload

  type MessagePayload {
    type: MessagePayloadType!
    cursor: String
    node: Message!
  }

  type SourceClearedPayload {
    source: Source
  }

  type ProjectManagerLayout {
    id: ID!
    selected: Source
    sources: [Source]
  }

  input ProjectManagerLayoutInput {
    selected: ID
    sources: [ID!]
  }

  type ProcessInfo {
    isAndroidSimulatorSupported: Boolean
    isIosSimulatorSupported: Boolean
    webAppUrl: String
  }

  type Query {
    # The project this instance of the XDL server is serving.
    currentProject: Project!
    # Globally persisted user preferences.
    userSettings: UserSettings!
    # Layout of the sections in project manager
    projectManagerLayout: ProjectManagerLayout
    # Information about the current process
    processInfo: ProcessInfo
    # Current logged-in user
    user: User
  }

  type Mutation {
    # Opens the app in an iOS simulator or and Android device/emulator.
    openSimulator(platform: Platform!): OpenSimulatorResult
    # Publishes the current project to expo.io
    publishProject(releaseChannel: String): PublishProjectResult
    # Sends the project URL by email.
    sendProjectUrl(recipient: String!): SendProjectResult
    # Compresses the images in a project
    optimizeAssets: Project
    # Updates specified project settings.
    setProjectSettings(settings: ProjectSettingsInput!): Project
    # Update projectConfig
    setProjectConfig(input: ProjectConfigInput!): Project
    # Update the layout
    setProjectManagerLayout(input: ProjectManagerLayoutInput): ProjectManagerLayout
    # Update a last read status for a source
    updateLastRead(sourceId: ID!, lastReadCursor: String!): Source
    # Clear a log from a source
    clearMessages(sourceId: ID!): Source
  }

  type Subscription {
    # TODO(freiksenet): per-project log
    messages(after: String): MessageSubscriptionPayload
  }
`;

const messageResolvers = {
  level(message) {
    if (message.level <= _xdl.Logger.DEBUG) return 'DEBUG';
    if (message.level <= _xdl.Logger.INFO) return 'INFO';
    if (message.level <= _xdl.Logger.WARN) return 'WARN';
    return 'ERROR';
  }
};

const USER_SETTINGS_ID = 'UserSettings';
const PROJECT_MANAGER_LAYOUT_ID = 'ProjectManagerLayout';

const resolvers = {
  OpenSimulatorResult: {
    __resolveType(result) {
      if (result.success) {
        return 'OpenSimulatorSuccess';
      } else {
        return 'OpenSimulatorError';
      }
    }
  },
  Message: {
    __resolveType(message) {
      if (message.tag === 'device') {
        return 'DeviceMessage';
      } else if (message.issueId) {
        return 'Issue';
      } else if (message._metroEventType) {
        switch (message._metroEventType) {
          case 'METRO_INITIALIZE_STARTED':
            {
              return 'MetroInitializeStarted';
            }
          case 'BUILD_STARTED':
          case 'BUILD_PROGRESS':
            {
              return 'BuildProgress';
            }
          case 'FINISHED':
            {
              return 'BuildFinished';
            }
          case 'FAILED':
            {
              return 'BuildError';
            }
        }
      } else if (message._expoEventType === 'TUNNEL_READY') {
        return 'TunnelReady';
      }
      return 'LogMessage';
    }
  },
  Issue: (0, _extends3.default)({}, messageResolvers, {
    id(issue) {
      return `Issue:${issue.id}`;
    },
    source(issue, args, context) {
      return context.getIssuesSource();
    }
  }),
  LogMessage: (0, _extends3.default)({}, messageResolvers, {
    source(message, args, context) {
      return context.getProcessSource();
    }
  }),
  MetroInitializeStarted: (0, _extends3.default)({}, messageResolvers, {
    source(message, args, context) {
      return context.getProcessSource();
    }
  }),
  BuildProgress: (0, _extends3.default)({}, messageResolvers, {
    source(message, args, context) {
      return context.getProcessSource();
    }
  }),
  BuildFinished: (0, _extends3.default)({}, messageResolvers, {
    source(message, args, context) {
      return context.getProcessSource();
    }
  }),
  BuildError: (0, _extends3.default)({}, messageResolvers, {
    source(message, args, context) {
      return context.getProcessSource();
    }
  }),
  TunnelReady: (0, _extends3.default)({}, messageResolvers, {
    source(message, args, context) {
      return context.getProcessSource();
    }
  }),
  DeviceMessage: (0, _extends3.default)({}, messageResolvers, {
    source(message) {
      return { id: message.deviceId, name: message.deviceName };
    }
  }),
  MessageSubscriptionPayload: {
    __resolveType(payload) {
      if (payload.type === 'CLEARED') {
        return 'SourceClearedPayload';
      } else {
        return 'MessagePayload';
      }
    }
  },
  SourceClearedPayload: {
    source(payload, args, context) {
      return context.getSourceById(payload.sourceId);
    }
  },
  Project: {
    id(project) {
      return Buffer.from(project.projectDir).toString('base64');
    },
    manifestUrl(project) {
      return (0, _asyncToGenerator3.default)(function* () {
        if ((yield _xdl.Project.currentStatus(project.projectDir)) === 'running') {
          return _xdl.UrlUtils.constructManifestUrlAsync(project.projectDir);
        } else {
          return null;
        }
      })();
    },
    settings(project) {
      return _xdl.ProjectSettings.readAsync(project.projectDir);
    },
    config(project) {
      return (0, _asyncToGenerator3.default)(function* () {
        var _ref = yield _xdl.ProjectUtils.readConfigJsonAsync(project.projectDir);

        let exp = _ref.exp;

        return exp;
      })();
    },
    sources(project, args, context) {
      return context.getSources();
    },
    messages(project, args, context) {
      return context.getMessageConnection();
    }
  },
  ProjectSettings: {
    hostType(projectSettings) {
      if (_xdl.Config.offline && projectSettings.hostType === 'tunnel') {
        return 'lan';
      } else {
        return projectSettings.hostType;
      }
    }
  },
  Source: {
    __resolveType(source) {
      return source.__typename;
    }
  },
  Issues: {
    messages(source, args, context) {
      return context.getMessageConnection(source);
    }
  },
  Process: {
    messages(source, args, context) {
      return context.getMessageConnection(source);
    }
  },
  Device: {
    messages(source, args, context) {
      return context.getMessageConnection(source);
    }
  },
  ProjectManagerLayout: {
    id() {
      return PROJECT_MANAGER_LAYOUT_ID;
    },
    selected(layout, args, context) {
      const sources = context.getSources();
      return sources.find(source => source.id === layout.selected);
    },
    sources(layout, args, context) {
      const sources = context.getSources();
      if (!layout.sources) {
        return [];
      } else {
        return layout.sources.map(sourceId => sources.find(source => source.id === sourceId)).filter(source => source);
      }
    }
  },
  UserSettings: {
    id() {
      return USER_SETTINGS_ID;
    }
  },
  Query: {
    currentProject(parent, args, context) {
      return context.getCurrentProject();
    },
    userSettings() {
      return _xdl.UserSettings.readAsync();
    },
    projectManagerLayout(parent, args, context) {
      return context.getProjectManagerLayout();
    },
    processInfo(parent, args, context) {
      return (0, _asyncToGenerator3.default)(function* () {
        const currentProject = context.getCurrentProject();

        var _ref2 = yield _xdl.ProjectUtils.readConfigJsonAsync(currentProject.projectDir);

        const exp = _ref2.exp;

        return {
          isAndroidSimulatorSupported: _xdl.Android.isPlatformSupported(),
          isIosSimulatorSupported: _xdl.Simulator.isPlatformSupported(),
          webAppUrl: exp.platforms.includes('web') ? yield _xdl.UrlUtils.constructWebAppUrlAsync(currentProject.projectDir) : null
        };
      })();
    },
    user() {
      return (0, _asyncToGenerator3.default)(function* () {
        const username = yield _xdl.UserManager.getCurrentUsernameAsync();
        return { username };
      })();
    }
  },
  Mutation: {
    openSimulator(parent, { platform }, context) {
      return (0, _asyncToGenerator3.default)(function* () {
        const currentProject = context.getCurrentProject();
        let result;
        if (platform === 'ANDROID') {
          result = yield _xdl.Android.openProjectAsync(currentProject.projectDir);
        } else {
          result = yield _xdl.Simulator.openProjectAsync(currentProject.projectDir);
        }
        if (result.success) {
          return result;
        } else {
          return {
            success: false,
            error: result.error.toString()
          };
        }
      })();
    },
    publishProject(parent, { releaseChannel }, context) {
      return (0, _asyncToGenerator3.default)(function* () {
        var _context$getCurrentPr = context.getCurrentProject();

        const projectDir = _context$getCurrentPr.projectDir;

        try {
          const result = yield _xdl.Project.publishAsync(projectDir, { releaseChannel });
          return result;
        } catch (error) {
          _xdl.ProjectUtils.logError(projectDir, 'expo', error.message);
          throw error;
        }
      })();
    },
    setProjectSettings(parent, { settings }, context) {
      return (0, _asyncToGenerator3.default)(function* () {
        const currentProject = context.getCurrentProject();
        let updatedSettings = yield _xdl.ProjectSettings.setAsync(currentProject.projectDir, settings);
        return (0, _extends3.default)({}, currentProject, {
          settings: updatedSettings
        });
      })();
    },
    optimizeAssets(parent, { settings }, context) {
      return (0, _asyncToGenerator3.default)(function* () {
        const currentProject = context.getCurrentProject();
        yield _xdl.Project.optimizeAsync(currentProject.projectDir);
        return (0, _extends3.default)({}, currentProject);
      })();
    },
    setProjectConfig(parent, { input }, context) {
      return (0, _asyncToGenerator3.default)(function* () {
        const currentProject = context.getCurrentProject();
        const filteredInput = (0, _extends3.default)({}, input, {
          githubUrl: input.githubUrl.match(/^https:\/\/github.com\//) ? input.githubUrl : undefined
        });

        var _ref3 = yield ConfigUtils.writeConfigJsonAsync(currentProject.projectDir, filteredInput);

        let exp = _ref3.exp;

        return (0, _extends3.default)({}, currentProject, {
          config: exp
        });
      })();
    },
    sendProjectUrl(parent, { recipient }, context) {
      return (0, _asyncToGenerator3.default)(function* () {
        const currentProject = context.getCurrentProject();
        let url = yield _xdl.UrlUtils.constructManifestUrlAsync(currentProject.projectDir);
        let result = yield _xdl.Exp.sendAsync(recipient, url);
        yield _xdl.UserSettings.setAsync('sendTo', recipient);
        return { medium: result.medium, url };
      })();
    },
    setProjectManagerLayout(parent, { input }, context) {
      context.setProjectManagerLayout(input);
      return context.getProjectManagerLayout();
    },
    updateLastRead(parent, { sourceId, lastReadCursor }, context) {
      const source = context.getSourceById(sourceId);
      if (source) {
        context.setLastRead(sourceId, lastReadCursor);
        return source;
      } else {
        return null;
      }
    },
    clearMessages(parent, { sourceId }, context) {
      const source = context.getSourceById(sourceId);
      context.clearMessages(source.id);
      return source;
    }
  },
  Subscription: {
    messages: {
      subscribe(parent, { after }, context) {
        let parsedCursor = null;
        if (after) {
          parsedCursor = parseInt(after, 10);
        }
        const issueIterator = context.getIssueIterator();
        const messageIterator = context.getMessageIterator(parsedCursor);
        const iterator = (0, _mergeAsyncIterators2.default)(issueIterator, messageIterator);
        return {
          next() {
            return (0, _asyncToGenerator3.default)(function* () {
              const result = yield iterator.next();
              const done = result.done,
                    value = result.value;

              return {
                value: {
                  messages: (0, _extends3.default)({}, value)
                },
                done
              };
            })();
          },

          [_iterall.$$asyncIterator]() {
            return this;
          }
        };
      }
    }
  }
};

exports.default = (0, _graphqlTools.makeExecutableSchema)({ typeDefs, resolvers });